Merge branch 'AppFlowy-IO:main' into main
@ -33,7 +33,7 @@ void main() {
|
||||
);
|
||||
|
||||
// tap the inline math equation button
|
||||
final inlineMathEquationButton = find.byTooltip(
|
||||
final inlineMathEquationButton = find.findFlowyTooltip(
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
);
|
||||
await tester.tapButton(inlineMathEquationButton);
|
||||
@ -78,7 +78,7 @@ void main() {
|
||||
);
|
||||
|
||||
// tap the inline math equation button
|
||||
var inlineMathEquationButton = find.byTooltip(
|
||||
var inlineMathEquationButton = find.findFlowyTooltip(
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
);
|
||||
await tester.tapButton(inlineMathEquationButton);
|
||||
@ -93,11 +93,11 @@ void main() {
|
||||
);
|
||||
|
||||
// expect to the see the inline math equation button is highlighted
|
||||
inlineMathEquationButton = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is SVGIconItemWidget &&
|
||||
widget.tooltip ==
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
inlineMathEquationButton = find.descendant(
|
||||
of: find.findFlowyTooltip(
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
),
|
||||
matching: find.byType(SVGIconItemWidget),
|
||||
);
|
||||
expect(
|
||||
tester.widget<SVGIconItemWidget>(inlineMathEquationButton).isHighlight,
|
||||
|
@ -1,113 +0,0 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_widget.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/mock/mock_openai_repository.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
const service = TestWorkspaceService(TestWorkspace.aiWorkSpace);
|
||||
|
||||
group('integration tests for open-ai smart menu', () {
|
||||
setUpAll(() async => service.setUpAll());
|
||||
setUp(() async => service.setUp());
|
||||
|
||||
testWidgets('testing selection on open-ai smart menu replace',
|
||||
(tester) async {
|
||||
final appFlowyEditor = await setUpOpenAITesting(tester);
|
||||
final editorState = appFlowyEditor.editorState;
|
||||
|
||||
editorState.service.selectionService.updateSelection(
|
||||
Selection(
|
||||
start: Position(path: [1], offset: 4),
|
||||
end: Position(path: [1], offset: 10),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(ToolbarWidget), findsAtLeastNWidgets(1));
|
||||
|
||||
await tester.tap(find.byTooltip('AI Assistants'));
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
await tester.tap(find.text('Summarize'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester
|
||||
.tap(find.byType(FlowyRichTextButton, skipOffstage: false).first);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
editorState.service.selectionService.currentSelection.value,
|
||||
Selection(
|
||||
start: Position(path: [1], offset: 4),
|
||||
end: Position(path: [1], offset: 84),
|
||||
),
|
||||
);
|
||||
});
|
||||
testWidgets('testing selection on open-ai smart menu insert',
|
||||
(tester) async {
|
||||
final appFlowyEditor = await setUpOpenAITesting(tester);
|
||||
final editorState = appFlowyEditor.editorState;
|
||||
|
||||
editorState.service.selectionService.updateSelection(
|
||||
Selection(
|
||||
start: Position(path: [1]),
|
||||
end: Position(path: [1], offset: 5),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(ToolbarWidget), findsAtLeastNWidgets(1));
|
||||
|
||||
await tester.tap(find.byTooltip('AI Assistants'));
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
await tester.tap(find.text('Summarize'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester
|
||||
.tap(find.byType(FlowyRichTextButton, skipOffstage: false).at(1));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
editorState.service.selectionService.currentSelection.value,
|
||||
Selection(
|
||||
start: Position(path: [2]),
|
||||
end: Position(path: [3]),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<AppFlowyEditor> setUpOpenAITesting(WidgetTester tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await mockOpenAIRepository();
|
||||
|
||||
await simulateKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||
await simulateKeyDownEvent(LogicalKeyboardKey.backslash);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Finder editor = find.byType(AppFlowyEditor);
|
||||
await tester.tap(editor);
|
||||
await tester.pumpAndSettle();
|
||||
return tester.state(editor).widget as AppFlowyEditor;
|
||||
}
|
||||
|
||||
Future<void> mockOpenAIRepository() async {
|
||||
await getIt.unregister<AIRepository>();
|
||||
getIt.registerFactoryAsync<AIRepository>(
|
||||
() => Future.value(
|
||||
MockOpenAIRepository(),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/env/cloud_env_test.dart';
|
||||
import 'package:appflowy/startup/entry_point.dart';
|
||||
@ -16,6 +13,8 @@ import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widget
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
@ -231,6 +230,16 @@ extension AppFlowyFinderTestBase on CommonFinders {
|
||||
(widget) => widget is FlowyText && widget.text == text,
|
||||
);
|
||||
}
|
||||
|
||||
Finder findFlowyTooltip(String richMessage, {bool skipOffstage = true}) {
|
||||
return byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is FlowyTooltip &&
|
||||
widget.richMessage != null &&
|
||||
widget.richMessage!.toPlainText().contains(richMessage),
|
||||
skipOffstage: skipOffstage,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> useTestSupabaseCloud() async {
|
||||
|
@ -3,7 +3,7 @@ import 'dart:ui';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/emoji_skin_tone.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_editor.dart';
|
||||
|
@ -175,7 +175,7 @@ SPEC CHECKSUMS:
|
||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
fluttertoast: 723e187574b149e68e63ca4d39b837586b903cfa
|
||||
fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
|
||||
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
||||
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
|
||||
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
|
||||
@ -197,4 +197,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: d0d9b4ff572d8695c38eb3f9b490f55cdfc57eca
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
COCOAPODS: 1.15.2
|
||||
|
@ -5,6 +5,7 @@ import 'package:appflowy/user/application/reminder/reminder_extension.dart';
|
||||
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
|
||||
import 'package:appflowy/workspace/application/settings/date_time/time_format_ext.dart';
|
||||
import 'package:appflowy/workspace/application/view/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
@ -23,16 +24,20 @@ class NotificationReminderBloc
|
||||
await event.when(
|
||||
initial: (reminder, dateFormat, timeFormat) async {
|
||||
this.reminder = reminder;
|
||||
this.dateFormat = dateFormat;
|
||||
this.timeFormat = timeFormat;
|
||||
|
||||
add(const NotificationReminderEvent.reset());
|
||||
},
|
||||
reset: () async {
|
||||
final createdAt = await _getCreatedAt(
|
||||
reminder,
|
||||
dateFormat,
|
||||
timeFormat,
|
||||
);
|
||||
final view = await _getView(reminder);
|
||||
final node = await _getContent(reminder);
|
||||
|
||||
if (view == null || node == null) {
|
||||
if (view == null) {
|
||||
emit(
|
||||
NotificationReminderState(
|
||||
createdAt: createdAt,
|
||||
@ -41,25 +46,43 @@ class NotificationReminderBloc
|
||||
status: NotificationReminderStatus.error,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
}
|
||||
|
||||
final layout = view!.layout;
|
||||
|
||||
if (layout.isDocumentView) {
|
||||
final node = await _getContent(reminder);
|
||||
if (node != null) {
|
||||
emit(
|
||||
NotificationReminderState(
|
||||
createdAt: createdAt,
|
||||
pageTitle: view.name,
|
||||
view: view,
|
||||
reminderContent: node.delta?.toPlainText() ?? '',
|
||||
nodes: [node],
|
||||
status: NotificationReminderStatus.loaded,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else if (layout.isDatabaseView) {
|
||||
emit(
|
||||
NotificationReminderState(
|
||||
createdAt: createdAt,
|
||||
pageTitle: view.name,
|
||||
view: view,
|
||||
reminderContent: node.delta?.toPlainText() ?? '',
|
||||
nodes: [node],
|
||||
reminderContent: reminder.message,
|
||||
status: NotificationReminderStatus.loaded,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
reset: () {},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
late final ReminderPB reminder;
|
||||
late final UserDateFormatPB dateFormat;
|
||||
late final UserTimeFormatPB timeFormat;
|
||||
|
||||
Future<String> _getCreatedAt(
|
||||
ReminderPB reminder,
|
||||
|
@ -46,10 +46,18 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
// control the app bar opacity when in immersive mode
|
||||
final ValueNotifier<double> _appBarOpacity = ValueNotifier(1.0);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
getIt<ReminderBloc>().add(const ReminderEvent.started());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_appBarOpacity.dispose();
|
||||
_scrollNotificationObserver = null;
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -78,8 +86,7 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
ViewBloc(view: view)..add(const ViewEvent.initial()),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: getIt<ReminderBloc>()
|
||||
..add(const ReminderEvent.started()),
|
||||
value: getIt<ReminderBloc>(),
|
||||
),
|
||||
if (view.layout.isDocumentView)
|
||||
BlocProvider(
|
||||
|
@ -99,6 +99,7 @@ class _MobileHomePageState extends State<MobileHomePage> {
|
||||
super.initState();
|
||||
|
||||
getIt<MenuSharedState>().addLatestViewListener(_onLatestViewChange);
|
||||
getIt<ReminderBloc>().add(const ReminderEvent.started());
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -35,6 +35,8 @@ class MobileSpaceHeader extends StatelessWidget {
|
||||
SpaceIcon(
|
||||
dimension: 24,
|
||||
space: space,
|
||||
svgSize: 14,
|
||||
textDimension: 18.0,
|
||||
cornerRadius: 6.0,
|
||||
),
|
||||
const HSpace(8),
|
||||
|
@ -74,6 +74,8 @@ class _SidebarSpaceMenuItem extends StatelessWidget {
|
||||
leftIcon: SpaceIcon(
|
||||
dimension: 24,
|
||||
space: space,
|
||||
svgSize: 14,
|
||||
textDimension: 18.0,
|
||||
cornerRadius: 6.0,
|
||||
),
|
||||
leftIconSize: const Size.square(24),
|
||||
|
@ -6,6 +6,8 @@ import 'package:appflowy/mobile/presentation/notifications/widgets/color.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_extension.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -76,7 +78,7 @@ class UnreadRedDot extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationContent extends StatelessWidget {
|
||||
class NotificationContent extends StatefulWidget {
|
||||
const NotificationContent({
|
||||
super.key,
|
||||
required this.reminder,
|
||||
@ -84,10 +86,29 @@ class NotificationContent extends StatelessWidget {
|
||||
|
||||
final ReminderPB reminder;
|
||||
|
||||
@override
|
||||
State<NotificationContent> createState() => _NotificationContentState();
|
||||
}
|
||||
|
||||
class _NotificationContentState extends State<NotificationContent> {
|
||||
@override
|
||||
void didUpdateWidget(covariant NotificationContent oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
context.read<NotificationReminderBloc>().add(
|
||||
const NotificationReminderEvent.reset(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<NotificationReminderBloc, NotificationReminderState>(
|
||||
builder: (context, state) {
|
||||
final view = state.view;
|
||||
if (view == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -105,15 +126,7 @@ class NotificationContent extends StatelessWidget {
|
||||
// content
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: IntrinsicHeight(
|
||||
child: BlocProvider(
|
||||
create: (context) => DocumentPageStyleBloc(view: state.view!),
|
||||
child: NotificationDocumentContent(
|
||||
reminder: reminder,
|
||||
nodes: state.nodes,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: _buildContent(view, nodes: state.nodes),
|
||||
),
|
||||
],
|
||||
);
|
||||
@ -121,6 +134,33 @@ class NotificationContent extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(ViewPB view, {List<Node>? nodes}) {
|
||||
if (view.layout.isDocumentView && nodes != null) {
|
||||
return IntrinsicHeight(
|
||||
child: BlocProvider(
|
||||
create: (context) => DocumentPageStyleBloc(view: view),
|
||||
child: NotificationDocumentContent(
|
||||
reminder: widget.reminder,
|
||||
nodes: nodes,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (view.layout.isDatabaseView) {
|
||||
final opacity = widget.reminder.type == ReminderType.past ? 0.3 : 1.0;
|
||||
return Opacity(
|
||||
opacity: opacity,
|
||||
child: FlowyText(
|
||||
widget.reminder.message,
|
||||
fontSize: 14,
|
||||
figmaLineHeight: 22,
|
||||
color: context.notificationItemTextColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return FlowyText.semibold(
|
||||
LocaleKeys.settings_notifications_titles_reminder.tr(),
|
||||
|
@ -26,6 +26,8 @@ part 'chat_bloc.g.dart';
|
||||
part 'chat_bloc.freezed.dart';
|
||||
|
||||
const sendMessageErrorKey = "sendMessageError";
|
||||
const systemUserId = "system";
|
||||
const aiResponseUserId = "0";
|
||||
|
||||
class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
ChatBloc({
|
||||
@ -87,6 +89,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
},
|
||||
);
|
||||
},
|
||||
// Loading messages
|
||||
startLoadingPrevMessage: () async {
|
||||
Int64? beforeMessageId;
|
||||
final oldestMessage = _getOlderstMessage();
|
||||
@ -130,21 +133,58 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
),
|
||||
);
|
||||
},
|
||||
// streaming message
|
||||
streaming: (Message message) {
|
||||
final allMessages = _perminentMessages();
|
||||
allMessages.insert(0, message);
|
||||
emit(
|
||||
state.copyWith(
|
||||
messages: allMessages,
|
||||
streamingStatus: const LoadingState.loading(),
|
||||
streamingState: const StreamingState.streaming(),
|
||||
canSendMessage: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
didFinishStreaming: () {
|
||||
finishStreaming: () {
|
||||
emit(
|
||||
state.copyWith(streamingStatus: const LoadingState.finish()),
|
||||
state.copyWith(
|
||||
streamingState: const StreamingState.done(),
|
||||
canSendMessage:
|
||||
state.sendingState == const SendMessageState.done(),
|
||||
),
|
||||
);
|
||||
},
|
||||
didUpdateAnswerStream: (AnswerStream stream) {
|
||||
emit(state.copyWith(answerStream: stream));
|
||||
},
|
||||
stopStream: () async {
|
||||
if (state.answerStream == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final payload = StopStreamPB(chatId: chatId);
|
||||
await AIEventStopStream(payload).send();
|
||||
final allMessages = _perminentMessages();
|
||||
if (state.streamingState != const StreamingState.done()) {
|
||||
// If the streaming is not started, remove the message from the list
|
||||
if (!state.answerStream!.hasStarted) {
|
||||
allMessages.removeWhere(
|
||||
(element) => element.id == lastStreamMessageId,
|
||||
);
|
||||
lastStreamMessageId = "";
|
||||
}
|
||||
|
||||
// when stop stream, we will set the answer stream to null. Which means the streaming
|
||||
// is finished or canceled.
|
||||
emit(
|
||||
state.copyWith(
|
||||
messages: allMessages,
|
||||
answerStream: null,
|
||||
streamingState: const StreamingState.done(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
receveMessage: (Message message) {
|
||||
final allMessages = _perminentMessages();
|
||||
// remove message with the same id
|
||||
@ -164,15 +204,32 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
lastSentMessage: null,
|
||||
messages: allMessages,
|
||||
relatedQuestions: [],
|
||||
sendingState: const SendMessageState.sending(),
|
||||
canSendMessage: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
finishSending: (ChatMessagePB message) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
lastSentMessage: message,
|
||||
sendingState: const SendMessageState.done(),
|
||||
canSendMessage:
|
||||
state.streamingState == const StreamingState.done(),
|
||||
),
|
||||
);
|
||||
},
|
||||
// related question
|
||||
didReceiveRelatedQuestion: (List<RelatedQuestionPB> questions) {
|
||||
if (questions.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final allMessages = _perminentMessages();
|
||||
final message = CustomMessage(
|
||||
metadata: OnetimeShotType.relatedQuestion.toMap(),
|
||||
author: const User(id: "system"),
|
||||
id: 'system',
|
||||
author: const User(id: systemUserId),
|
||||
id: systemUserId,
|
||||
);
|
||||
allMessages.insert(0, message);
|
||||
emit(
|
||||
@ -189,44 +246,6 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
),
|
||||
);
|
||||
},
|
||||
didSentUserMessage: (ChatMessagePB message) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
lastSentMessage: message,
|
||||
),
|
||||
);
|
||||
},
|
||||
didUpdateAnswerStream: (AnswerStream stream) {
|
||||
emit(state.copyWith(answerStream: stream));
|
||||
},
|
||||
stopStream: () async {
|
||||
if (state.answerStream == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final payload = StopStreamPB(chatId: chatId);
|
||||
await AIEventStopStream(payload).send();
|
||||
final allMessages = _perminentMessages();
|
||||
if (state.streamingStatus != const LoadingState.finish()) {
|
||||
// If the streaming is not started, remove the message from the list
|
||||
if (!state.answerStream!.hasStarted) {
|
||||
allMessages.removeWhere(
|
||||
(element) => element.id == lastStreamMessageId,
|
||||
);
|
||||
lastStreamMessageId = "";
|
||||
}
|
||||
|
||||
// when stop stream, we will set the answer stream to null. Which means the streaming
|
||||
// is finished or canceled.
|
||||
emit(
|
||||
state.copyWith(
|
||||
messages: allMessages,
|
||||
answerStream: null,
|
||||
streamingStatus: const LoadingState.finish(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -250,7 +269,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
chatErrorMessageCallback: (err) {
|
||||
if (!isClosed) {
|
||||
Log.error("chat error: ${err.errorMessage}");
|
||||
add(const ChatEvent.didFinishStreaming());
|
||||
add(const ChatEvent.finishStreaming());
|
||||
}
|
||||
},
|
||||
latestMessageCallback: (list) {
|
||||
@ -267,7 +286,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
},
|
||||
finishStreamingCallback: () {
|
||||
if (!isClosed) {
|
||||
add(const ChatEvent.didFinishStreaming());
|
||||
add(const ChatEvent.finishStreaming());
|
||||
// The answer strema will bet set to null after the streaming is finished or canceled.
|
||||
// so if the answer stream is null, we will not get related question.
|
||||
if (state.lastSentMessage != null && state.answerStream != null) {
|
||||
@ -353,7 +372,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
result.fold(
|
||||
(ChatMessagePB question) {
|
||||
if (!isClosed) {
|
||||
add(ChatEvent.didSentUserMessage(question));
|
||||
add(ChatEvent.finishSending(question));
|
||||
|
||||
final questionMessageId = question.messageId;
|
||||
final message = _createTextMessage(question);
|
||||
@ -374,8 +393,8 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
|
||||
final error = CustomMessage(
|
||||
metadata: metadata,
|
||||
author: const User(id: "system"),
|
||||
id: 'system',
|
||||
author: const User(id: systemUserId),
|
||||
id: systemUserId,
|
||||
);
|
||||
|
||||
add(ChatEvent.receveMessage(error));
|
||||
@ -390,7 +409,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
lastStreamMessageId = streamMessageId;
|
||||
|
||||
return TextMessage(
|
||||
author: User(id: nanoid()),
|
||||
author: User(id: "streamId:${nanoid()}"),
|
||||
metadata: {
|
||||
"$AnswerStream": stream,
|
||||
"question": questionMessageId,
|
||||
@ -425,10 +444,21 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
@freezed
|
||||
class ChatEvent with _$ChatEvent {
|
||||
const factory ChatEvent.initialLoad() = _InitialLoadMessage;
|
||||
|
||||
// send message
|
||||
const factory ChatEvent.sendMessage({
|
||||
required String message,
|
||||
Map<String, dynamic>? metadata,
|
||||
}) = _SendMessage;
|
||||
const factory ChatEvent.finishSending(ChatMessagePB message) =
|
||||
_FinishSendMessage;
|
||||
|
||||
// receive message
|
||||
const factory ChatEvent.streaming(Message message) = _StreamingMessage;
|
||||
const factory ChatEvent.receveMessage(Message message) = _ReceiveMessage;
|
||||
const factory ChatEvent.finishStreaming() = _FinishStreamingMessage;
|
||||
|
||||
// loading messages
|
||||
const factory ChatEvent.startLoadingPrevMessage() = _StartLoadPrevMessage;
|
||||
const factory ChatEvent.didLoadPreviousMessages(
|
||||
List<Message> messages,
|
||||
@ -436,16 +466,13 @@ class ChatEvent with _$ChatEvent {
|
||||
) = _DidLoadPreviousMessages;
|
||||
const factory ChatEvent.didLoadLatestMessages(List<Message> messages) =
|
||||
_DidLoadMessages;
|
||||
const factory ChatEvent.streaming(Message message) = _StreamingMessage;
|
||||
const factory ChatEvent.receveMessage(Message message) = _ReceiveMessage;
|
||||
|
||||
const factory ChatEvent.didFinishStreaming() = _FinishStreamingMessage;
|
||||
// related questions
|
||||
const factory ChatEvent.didReceiveRelatedQuestion(
|
||||
List<RelatedQuestionPB> questions,
|
||||
) = _DidReceiveRelatedQueston;
|
||||
const factory ChatEvent.clearReleatedQuestion() = _ClearRelatedQuestion;
|
||||
const factory ChatEvent.didSentUserMessage(ChatMessagePB message) =
|
||||
_DidSendUserMessage;
|
||||
|
||||
const factory ChatEvent.didUpdateAnswerStream(
|
||||
AnswerStream stream,
|
||||
) = _DidUpdateAnswerStream;
|
||||
@ -466,7 +493,8 @@ class ChatState with _$ChatState {
|
||||
required LoadingState loadingPreviousStatus,
|
||||
// When sending a user message, the status will be set as loading.
|
||||
// After the message is sent, the status will be set as finished.
|
||||
required LoadingState streamingStatus,
|
||||
required StreamingState streamingState,
|
||||
required SendMessageState sendingState,
|
||||
// Indicate whether there are more previous messages to load.
|
||||
required bool hasMorePrevMessage,
|
||||
// The related questions that are received after the user message is sent.
|
||||
@ -474,6 +502,7 @@ class ChatState with _$ChatState {
|
||||
// The last user message that is sent to the server.
|
||||
ChatMessagePB? lastSentMessage,
|
||||
AnswerStream? answerStream,
|
||||
@Default(true) bool canSendMessage,
|
||||
}) = _ChatState;
|
||||
|
||||
factory ChatState.initial(ViewPB view, UserProfilePB userProfile) =>
|
||||
@ -483,12 +512,19 @@ class ChatState with _$ChatState {
|
||||
userProfile: userProfile,
|
||||
initialLoadingStatus: const LoadingState.finish(),
|
||||
loadingPreviousStatus: const LoadingState.finish(),
|
||||
streamingStatus: const LoadingState.finish(),
|
||||
streamingState: const StreamingState.done(),
|
||||
sendingState: const SendMessageState.done(),
|
||||
hasMorePrevMessage: true,
|
||||
relatedQuestions: [],
|
||||
);
|
||||
}
|
||||
|
||||
bool isOtherUserMessage(Message message) {
|
||||
return message.author.id != aiResponseUserId &&
|
||||
message.author.id != systemUserId &&
|
||||
!message.author.id.startsWith("streamId:");
|
||||
}
|
||||
|
||||
@freezed
|
||||
class LoadingState with _$LoadingState {
|
||||
const factory LoadingState.loading() = _Loading;
|
||||
@ -497,6 +533,7 @@ class LoadingState with _$LoadingState {
|
||||
|
||||
enum OnetimeShotType {
|
||||
unknown,
|
||||
sendingMessage,
|
||||
relatedQuestion,
|
||||
invalidSendMesssage,
|
||||
}
|
||||
@ -638,7 +675,9 @@ List<ChatMessageMetadata> chatMessageMetadataFromString(String? s) {
|
||||
}
|
||||
|
||||
if (metadataJson is Map<String, dynamic>) {
|
||||
metadata.add(ChatMessageMetadata.fromJson(metadataJson));
|
||||
if (metadataJson.isNotEmpty) {
|
||||
metadata.add(ChatMessageMetadata.fromJson(metadataJson));
|
||||
}
|
||||
} else if (metadataJson is List) {
|
||||
metadata.addAll(
|
||||
metadataJson.map(
|
||||
@ -672,3 +711,15 @@ class ChatMessageMetadata {
|
||||
|
||||
Map<String, dynamic> toJson() => _$ChatMessageMetadataToJson(this);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class StreamingState with _$StreamingState {
|
||||
const factory StreamingState.streaming() = _Streaming;
|
||||
const factory StreamingState.done({FlowyError? error}) = _StreamDone;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SendMessageState with _$SendMessageState {
|
||||
const factory SendMessageState.sending() = _Sending;
|
||||
const factory SendMessageState.done({FlowyError? error}) = _SendDone;
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'chat_input_bloc.dart';
|
||||
|
||||
part 'chat_file_bloc.freezed.dart';
|
||||
|
||||
class ChatFileBloc extends Bloc<ChatFileEvent, ChatFileState> {
|
||||
@ -46,9 +48,17 @@ class ChatFileBloc extends Bloc<ChatFileEvent, ChatFileState> {
|
||||
);
|
||||
},
|
||||
newFile: (String filePath, String fileName) async {
|
||||
final files = List<ChatFile>.from(state.uploadFiles);
|
||||
files.add(ChatFile(filePath: filePath, fileName: fileName));
|
||||
emit(
|
||||
state.copyWith(
|
||||
indexFileIndicator: IndexFileIndicator.indexing(fileName),
|
||||
uploadFiles: files,
|
||||
),
|
||||
);
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
uploadFileIndicator: UploadFileIndicator.uploading(fileName),
|
||||
),
|
||||
);
|
||||
final payload = ChatFilePB(filePath: filePath, chatId: chatId);
|
||||
@ -57,14 +67,14 @@ class ChatFileBloc extends Bloc<ChatFileEvent, ChatFileState> {
|
||||
if (!isClosed) {
|
||||
result.fold((_) {
|
||||
add(
|
||||
ChatFileEvent.updateIndexFile(
|
||||
IndexFileIndicator.finish(fileName),
|
||||
ChatFileEvent.updateUploadState(
|
||||
UploadFileIndicator.finish(fileName),
|
||||
),
|
||||
);
|
||||
}, (err) {
|
||||
add(
|
||||
ChatFileEvent.updateIndexFile(
|
||||
IndexFileIndicator.error(err.msg),
|
||||
ChatFileEvent.updateUploadState(
|
||||
UploadFileIndicator.error(err.msg),
|
||||
),
|
||||
);
|
||||
});
|
||||
@ -83,16 +93,31 @@ class ChatFileBloc extends Bloc<ChatFileEvent, ChatFileState> {
|
||||
),
|
||||
);
|
||||
},
|
||||
updateIndexFile: (IndexFileIndicator indicator) {
|
||||
emit(
|
||||
state.copyWith(indexFileIndicator: indicator),
|
||||
);
|
||||
},
|
||||
updatePluginState: (LocalAIPluginStatePB chatState) {
|
||||
final fileEnabled = state.chatState?.fileEnabled ?? false;
|
||||
final supportChatWithFile =
|
||||
fileEnabled && chatState.state == RunningStatePB.Running;
|
||||
emit(state.copyWith(supportChatWithFile: supportChatWithFile));
|
||||
|
||||
final aiType = chatState.state == RunningStatePB.Running
|
||||
? const AIType.localAI()
|
||||
: const AIType.appflowyAI();
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
supportChatWithFile: supportChatWithFile,
|
||||
aiType: aiType,
|
||||
),
|
||||
);
|
||||
},
|
||||
clear: () {
|
||||
emit(
|
||||
state.copyWith(
|
||||
uploadFiles: [],
|
||||
),
|
||||
);
|
||||
},
|
||||
updateUploadState: (UploadFileIndicator indicator) {
|
||||
emit(state.copyWith(uploadFileIndicator: indicator));
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -113,27 +138,40 @@ class ChatFileEvent with _$ChatFileEvent {
|
||||
const factory ChatFileEvent.initial() = Initial;
|
||||
const factory ChatFileEvent.newFile(String filePath, String fileName) =
|
||||
_NewFile;
|
||||
const factory ChatFileEvent.clear() = _ClearFile;
|
||||
const factory ChatFileEvent.updateUploadState(UploadFileIndicator indicator) =
|
||||
_UpdateUploadState;
|
||||
const factory ChatFileEvent.updateChatState(LocalAIChatPB chatState) =
|
||||
_UpdateChatState;
|
||||
const factory ChatFileEvent.updatePluginState(
|
||||
LocalAIPluginStatePB chatState,
|
||||
) = _UpdatePluginState;
|
||||
const factory ChatFileEvent.updateIndexFile(IndexFileIndicator indicator) =
|
||||
_UpdateIndexFile;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChatFileState with _$ChatFileState {
|
||||
const factory ChatFileState({
|
||||
@Default(false) bool supportChatWithFile,
|
||||
IndexFileIndicator? indexFileIndicator,
|
||||
UploadFileIndicator? uploadFileIndicator,
|
||||
LocalAIChatPB? chatState,
|
||||
@Default([]) List<ChatFile> uploadFiles,
|
||||
@Default(AIType.appflowyAI()) AIType aiType,
|
||||
}) = _ChatFileState;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class IndexFileIndicator with _$IndexFileIndicator {
|
||||
const factory IndexFileIndicator.finish(String fileName) = _Finish;
|
||||
const factory IndexFileIndicator.indexing(String fileName) = _Indexing;
|
||||
const factory IndexFileIndicator.error(String error) = _Error;
|
||||
class UploadFileIndicator with _$UploadFileIndicator {
|
||||
const factory UploadFileIndicator.finish(String fileName) = _Finish;
|
||||
const factory UploadFileIndicator.uploading(String fileName) = _Uploading;
|
||||
const factory UploadFileIndicator.error(String error) = _Error;
|
||||
}
|
||||
|
||||
class ChatFile {
|
||||
ChatFile({
|
||||
required this.filePath,
|
||||
required this.fileName,
|
||||
});
|
||||
|
||||
final String filePath;
|
||||
final String fileName;
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import 'package:flutter/widgets.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'chat_input_action_control.dart';
|
||||
|
||||
part 'chat_input_action_bloc.freezed.dart';
|
||||
|
||||
class ChatInputActionBloc
|
||||
@ -34,7 +35,12 @@ class ChatInputActionBloc
|
||||
final views = result
|
||||
.toNullable()
|
||||
?.items
|
||||
.where((v) => v.layout.isDocumentView)
|
||||
.where(
|
||||
(v) =>
|
||||
v.layout.isDocumentView &&
|
||||
!v.isSpace &&
|
||||
v.parentViewId.isNotEmpty,
|
||||
)
|
||||
.toList() ??
|
||||
[];
|
||||
if (!isClosed) {
|
||||
@ -108,6 +114,14 @@ class ChatInputActionBloc
|
||||
),
|
||||
);
|
||||
},
|
||||
clear: () {
|
||||
emit(
|
||||
state.copyWith(
|
||||
selectedPages: [],
|
||||
filter: "",
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -171,6 +185,7 @@ class ChatInputActionEvent with _$ChatInputActionEvent {
|
||||
const factory ChatInputActionEvent.addPage(ChatInputActionPage page) =
|
||||
_AddPage;
|
||||
const factory ChatInputActionEvent.removePage(String text) = _RemovePage;
|
||||
const factory ChatInputActionEvent.clear() = _Clear;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -35,10 +35,18 @@ class ChatInputActionControl extends ChatActionHandler {
|
||||
List<String> get tags =>
|
||||
_commandBloc.state.selectedPages.map((e) => e.title).toList();
|
||||
|
||||
ChatInputMetadata get metaData => _commandBloc.state.selectedPages.fold(
|
||||
<String, ChatInputActionPage>{},
|
||||
(map, page) => map..putIfAbsent(page.pageId, () => page),
|
||||
);
|
||||
ChatInputMetadata consumeMetaData() {
|
||||
final metadata = _commandBloc.state.selectedPages.fold(
|
||||
<String, ChatInputActionPage>{},
|
||||
(map, page) => map..putIfAbsent(page.pageId, () => page),
|
||||
);
|
||||
|
||||
if (metadata.isNotEmpty) {
|
||||
_commandBloc.add(const ChatInputActionEvent.clear());
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
void handleKeyEvent(KeyEvent event) {
|
||||
// ignore: deprecated_member_use
|
||||
|
@ -0,0 +1,80 @@
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'chat_member_bloc.freezed.dart';
|
||||
|
||||
class ChatMemberBloc extends Bloc<ChatMemberEvent, ChatMemberState> {
|
||||
ChatMemberBloc() : super(const ChatMemberState()) {
|
||||
on<ChatMemberEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () {},
|
||||
receiveMemberInfo: (String id, WorkspaceMemberPB memberInfo) {
|
||||
final members = Map<String, ChatMember>.from(state.members);
|
||||
members[id] = ChatMember(info: memberInfo);
|
||||
emit(state.copyWith(members: members));
|
||||
},
|
||||
getMemberInfo: (String userId) {
|
||||
if (state.members.containsKey(userId)) {
|
||||
// Member info already exists. Debouncing refresh member info from backend would be better.
|
||||
return;
|
||||
}
|
||||
|
||||
final payload = WorkspaceMemberIdPB(
|
||||
uid: Int64.parseInt(userId),
|
||||
);
|
||||
UserEventGetMemberInfo(payload).send().then((result) {
|
||||
if (!isClosed) {
|
||||
result.fold((member) {
|
||||
add(
|
||||
ChatMemberEvent.receiveMemberInfo(
|
||||
userId,
|
||||
member,
|
||||
),
|
||||
);
|
||||
}, (err) {
|
||||
Log.error("Error getting member info: $err");
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChatMemberEvent with _$ChatMemberEvent {
|
||||
const factory ChatMemberEvent.initial() = Initial;
|
||||
const factory ChatMemberEvent.getMemberInfo(
|
||||
String userId,
|
||||
) = _GetMemberInfo;
|
||||
const factory ChatMemberEvent.receiveMemberInfo(
|
||||
String id,
|
||||
WorkspaceMemberPB memberInfo,
|
||||
) = _ReceiveMemberInfo;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChatMemberState with _$ChatMemberState {
|
||||
const factory ChatMemberState({
|
||||
@Default({}) Map<String, ChatMember> members,
|
||||
}) = _ChatMemberState;
|
||||
}
|
||||
|
||||
class ChatMember extends Equatable {
|
||||
ChatMember({
|
||||
required this.info,
|
||||
});
|
||||
final DateTime _date = DateTime.now();
|
||||
final WorkspaceMemberPB info;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [_date, info];
|
||||
}
|
@ -7,7 +7,8 @@ import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
|
||||
Future<List<ChatMessageMetaPB>> metadataPBFromMetadata(
|
||||
Map<String, dynamic>? map,) async {
|
||||
Map<String, dynamic>? map,
|
||||
) async {
|
||||
final List<ChatMessageMetaPB> metadata = [];
|
||||
if (map != null) {
|
||||
for (final entry in map.entries) {
|
||||
@ -18,11 +19,14 @@ Future<List<ChatMessageMetaPB>> metadataPBFromMetadata(
|
||||
final payload = OpenDocumentPayloadPB(documentId: view.id);
|
||||
final result = await DocumentEventGetDocumentText(payload).send();
|
||||
result.fold((pb) {
|
||||
metadata.add(ChatMessageMetaPB(
|
||||
id: view.id,
|
||||
name: view.name,
|
||||
text: pb.text,
|
||||
),);
|
||||
metadata.add(
|
||||
ChatMessageMetaPB(
|
||||
id: view.id,
|
||||
name: view.name,
|
||||
data: pb.text,
|
||||
source: "appflowy document",
|
||||
),
|
||||
);
|
||||
}, (err) {
|
||||
Log.error('Failed to get document text: $err');
|
||||
});
|
||||
|
@ -1,7 +1,4 @@
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_member_bloc.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_chat_types/flutter_chat_types.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
@ -12,25 +9,14 @@ class ChatUserMessageBloc
|
||||
extends Bloc<ChatUserMessageEvent, ChatUserMessageState> {
|
||||
ChatUserMessageBloc({
|
||||
required Message message,
|
||||
}) : super(ChatUserMessageState.initial(message)) {
|
||||
required ChatMember? member,
|
||||
}) : super(ChatUserMessageState.initial(message, member)) {
|
||||
on<ChatUserMessageEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () {
|
||||
final payload =
|
||||
WorkspaceMemberIdPB(uid: Int64.parseInt(message.author.id));
|
||||
UserEventGetMemberInfo(payload).send().then((result) {
|
||||
if (!isClosed) {
|
||||
result.fold((member) {
|
||||
add(ChatUserMessageEvent.didReceiveMemberInfo(member));
|
||||
}, (err) {
|
||||
Log.error("Error getting member info: $err");
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
didReceiveMemberInfo: (WorkspaceMemberPB memberInfo) {
|
||||
emit(state.copyWith(member: memberInfo));
|
||||
initial: () {},
|
||||
refreshMember: (ChatMember member) {
|
||||
emit(state.copyWith(member: member));
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -41,18 +27,20 @@ class ChatUserMessageBloc
|
||||
@freezed
|
||||
class ChatUserMessageEvent with _$ChatUserMessageEvent {
|
||||
const factory ChatUserMessageEvent.initial() = Initial;
|
||||
const factory ChatUserMessageEvent.didReceiveMemberInfo(
|
||||
WorkspaceMemberPB memberInfo,
|
||||
) = _MemberInfo;
|
||||
const factory ChatUserMessageEvent.refreshMember(ChatMember member) =
|
||||
_MemberInfo;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChatUserMessageState with _$ChatUserMessageState {
|
||||
const factory ChatUserMessageState({
|
||||
required Message message,
|
||||
WorkspaceMemberPB? member,
|
||||
ChatMember? member,
|
||||
}) = _ChatUserMessageState;
|
||||
|
||||
factory ChatUserMessageState.initial(Message message) =>
|
||||
ChatUserMessageState(message: message);
|
||||
factory ChatUserMessageState.initial(
|
||||
Message message,
|
||||
ChatMember? member,
|
||||
) =>
|
||||
ChatUserMessageState(message: message, member: member);
|
||||
}
|
||||
|
@ -1,32 +1,32 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_file_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_input_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:flowy_infra/platform_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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/ai_message_bubble.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_file_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_input_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_related_question.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/user_message_bubble.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/message/ai_message_bubble.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/message/other_user_message_bubble.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/message/user_message_bubble.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/platform_extension.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_chat_types/flutter_chat_types.dart';
|
||||
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
|
||||
import 'package:flutter_chat_types/flutter_chat_types.dart';
|
||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart' show Chat;
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
import 'application/chat_member_bloc.dart';
|
||||
import 'application/chat_side_pannel_bloc.dart';
|
||||
import 'presentation/chat_input/chat_input.dart';
|
||||
import 'presentation/chat_popmenu.dart';
|
||||
@ -79,7 +79,6 @@ class AIChatPage extends StatelessWidget {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
/// [ChatBloc] is used to handle chat messages including send/receive message
|
||||
///
|
||||
BlocProvider(
|
||||
create: (_) => ChatBloc(
|
||||
view: view,
|
||||
@ -88,25 +87,24 @@ class AIChatPage extends StatelessWidget {
|
||||
),
|
||||
|
||||
/// [ChatFileBloc] is used to handle file indexing as a chat context
|
||||
///
|
||||
BlocProvider(
|
||||
create: (_) => ChatFileBloc(chatId: view.id.toString())
|
||||
create: (_) => ChatFileBloc(chatId: view.id)
|
||||
..add(const ChatFileEvent.initial()),
|
||||
),
|
||||
|
||||
/// [ChatInputStateBloc] is used to handle chat input text field state
|
||||
///
|
||||
BlocProvider(
|
||||
create: (_) =>
|
||||
ChatInputStateBloc()..add(const ChatInputStateEvent.started()),
|
||||
),
|
||||
BlocProvider(create: (_) => ChatSidePannelBloc(chatId: view.id)),
|
||||
BlocProvider(create: (_) => ChatMemberBloc()),
|
||||
],
|
||||
child: BlocListener<ChatFileBloc, ChatFileState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.indexFileIndicator != current.indexFileIndicator,
|
||||
previous.uploadFileIndicator != current.uploadFileIndicator,
|
||||
listener: (context, state) {
|
||||
_handleIndexIndicator(state.indexFileIndicator, context);
|
||||
_handleIndexIndicator(state.uploadFileIndicator, context);
|
||||
},
|
||||
child: BlocBuilder<ChatFileBloc, ChatFileState>(
|
||||
builder: (context, state) {
|
||||
@ -149,7 +147,7 @@ class AIChatPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
void _handleIndexIndicator(
|
||||
IndexFileIndicator? indicator,
|
||||
UploadFileIndicator? indicator,
|
||||
BuildContext context,
|
||||
) {
|
||||
if (indicator != null) {
|
||||
@ -160,7 +158,7 @@ class AIChatPage extends StatelessWidget {
|
||||
LocaleKeys.chat_indexFileSuccess.tr(args: [fileName]),
|
||||
);
|
||||
},
|
||||
indexing: (fileName) {
|
||||
uploading: (fileName) {
|
||||
showSnackBarMessage(
|
||||
context,
|
||||
LocaleKeys.chat_indexingFile.tr(args: [fileName]),
|
||||
@ -298,12 +296,13 @@ class _ChatContentPageState extends State<_ChatContentPage> {
|
||||
Widget buildChatWidget() {
|
||||
return BlocBuilder<ChatBloc, ChatState>(
|
||||
builder: (blocContext, state) => Chat(
|
||||
key: ValueKey(widget.view.id),
|
||||
messages: state.messages,
|
||||
onSendPressed: (_) {
|
||||
// We use custom bottom widget for chat input, so
|
||||
// do not need to handle this event.
|
||||
},
|
||||
customBottomWidget: buildChatInput(blocContext),
|
||||
customBottomWidget: buildBottom(blocContext),
|
||||
user: _user,
|
||||
theme: buildTheme(context),
|
||||
onEndReached: () async {
|
||||
@ -335,7 +334,7 @@ class _ChatContentPageState extends State<_ChatContentPage> {
|
||||
required messageWidth,
|
||||
required showName,
|
||||
}) =>
|
||||
_buildAITextMessage(blocContext, textMessage),
|
||||
_buildTextMessage(blocContext, textMessage),
|
||||
bubbleBuilder: (
|
||||
child, {
|
||||
required message,
|
||||
@ -346,17 +345,21 @@ class _ChatContentPageState extends State<_ChatContentPage> {
|
||||
message: message,
|
||||
child: child,
|
||||
);
|
||||
} else if (isOtherUserMessage(message)) {
|
||||
return OtherUserMessageBubble(
|
||||
message: message,
|
||||
child: child,
|
||||
);
|
||||
} else {
|
||||
return _buildAIBubble(message, blocContext, state, child);
|
||||
}
|
||||
|
||||
return _buildAIBubble(message, blocContext, state, child);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAITextMessage(BuildContext context, TextMessage message) {
|
||||
final isAuthor = message.author.id == _user.id;
|
||||
if (isAuthor) {
|
||||
Widget _buildTextMessage(BuildContext context, TextMessage message) {
|
||||
if (message.author.id == _user.id) {
|
||||
return ChatTextMessageWidget(
|
||||
user: message.author,
|
||||
messageUserId: message.id,
|
||||
@ -482,7 +485,7 @@ class _ChatContentPageState extends State<_ChatContentPage> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildChatInput(BuildContext context) {
|
||||
Widget buildBottom(BuildContext context) {
|
||||
return ClipRect(
|
||||
child: Padding(
|
||||
padding: AIChatUILayout.safeAreaInsets(context),
|
||||
@ -497,9 +500,9 @@ class _ChatContentPageState extends State<_ChatContentPage> {
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
BlocSelector<ChatBloc, ChatState, LoadingState>(
|
||||
selector: (state) => state.streamingStatus,
|
||||
builder: (context, state) {
|
||||
BlocSelector<ChatBloc, ChatState, bool>(
|
||||
selector: (state) => state.canSendMessage,
|
||||
builder: (context, canSendMessage) {
|
||||
return ChatInput(
|
||||
aiType: aiType,
|
||||
chatId: widget.view.id,
|
||||
@ -511,7 +514,7 @@ class _ChatContentPageState extends State<_ChatContentPage> {
|
||||
),
|
||||
);
|
||||
},
|
||||
isStreaming: state != const LoadingState.finish(),
|
||||
isStreaming: !canSendMessage,
|
||||
onStopStreaming: () {
|
||||
context
|
||||
.read<ChatBloc>()
|
||||
|
@ -8,6 +8,8 @@ import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:string_validator/string_validator.dart';
|
||||
|
||||
const defaultAvatarSize = 30.0;
|
||||
|
||||
class ChatChatUserAvatar extends StatelessWidget {
|
||||
const ChatChatUserAvatar({required this.userId, super.key});
|
||||
|
||||
@ -33,15 +35,18 @@ class ChatBorderedCircleAvatar extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CircleAvatar(
|
||||
backgroundColor: border.color,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints.expand(),
|
||||
child: CircleAvatar(
|
||||
backgroundImage: backgroundImage,
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
child: child,
|
||||
return SizedBox(
|
||||
width: defaultAvatarSize,
|
||||
child: CircleAvatar(
|
||||
backgroundColor: border.color,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints.expand(),
|
||||
child: CircleAvatar(
|
||||
backgroundImage: backgroundImage,
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -53,13 +58,15 @@ class ChatUserAvatar extends StatelessWidget {
|
||||
super.key,
|
||||
required this.iconUrl,
|
||||
required this.name,
|
||||
required this.size,
|
||||
this.size = defaultAvatarSize,
|
||||
this.isHovering = false,
|
||||
this.defaultName,
|
||||
});
|
||||
|
||||
final String iconUrl;
|
||||
final String name;
|
||||
final double size;
|
||||
final String? defaultName;
|
||||
|
||||
// If true, a border will be applied on top of the avatar
|
||||
final bool isHovering;
|
||||
@ -76,7 +83,8 @@ class ChatUserAvatar extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildEmptyAvatar(BuildContext context) {
|
||||
final String nameOrDefault = _userName(name);
|
||||
final String nameOrDefault = _userName(name, defaultName);
|
||||
|
||||
final Color color = ColorGenerator(name).toColor();
|
||||
const initialsCount = 2;
|
||||
|
||||
@ -170,8 +178,8 @@ class ChatUserAvatar extends StatelessWidget {
|
||||
/// Return the user name, if the user name is empty,
|
||||
/// return the default user name.
|
||||
///
|
||||
String _userName(String name) =>
|
||||
name.isEmpty ? LocaleKeys.defaultUsername.tr() : name;
|
||||
String _userName(String name, String? defaultName) =>
|
||||
name.isEmpty ? (defaultName ?? LocaleKeys.defaultUsername.tr()) : name;
|
||||
|
||||
/// Used to darken the generated color for the hover border effect.
|
||||
/// The color is darkened by 15% - Hence the 0.15 value.
|
||||
|
@ -0,0 +1,30 @@
|
||||
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/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
|
||||
class ChatInputAttachment extends StatelessWidget {
|
||||
const ChatInputAttachment({required this.onTap, super.key});
|
||||
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyTooltip(
|
||||
message: LocaleKeys.chat_uploadFile.tr(),
|
||||
child: FlowyIconButton(
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
radius: BorderRadius.circular(18),
|
||||
icon: FlowySvg(
|
||||
FlowySvgs.ai_attachment_s,
|
||||
size: const Size.square(20),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed: onTap,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,25 +1,31 @@
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_file_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_input_action_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_input_action_control.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_input_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_input_action_menu.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_input_action_control.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mobile_page_selector_sheet.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:extended_text_field/extended_text_field.dart';
|
||||
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||
import 'package:flowy_infra/platform_extension.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
|
||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
||||
|
||||
import 'chat_at_button.dart';
|
||||
import 'chat_send_button.dart';
|
||||
import 'chat_attachment.dart';
|
||||
import 'chat_input_span.dart';
|
||||
import 'chat_send_button.dart';
|
||||
|
||||
class ChatInput extends StatefulWidget {
|
||||
/// Creates [ChatInput] widget.
|
||||
const ChatInput({
|
||||
super.key,
|
||||
this.isAttachmentUploading,
|
||||
this.onAttachmentPressed,
|
||||
required this.onSendPressed,
|
||||
required this.chatId,
|
||||
@ -30,7 +36,6 @@ class ChatInput extends StatefulWidget {
|
||||
required this.aiType,
|
||||
});
|
||||
|
||||
final bool? isAttachmentUploading;
|
||||
final VoidCallback? onAttachmentPressed;
|
||||
final void Function(types.PartialText) onSendPressed;
|
||||
final void Function() onStopStreaming;
|
||||
@ -59,7 +64,6 @@ class _ChatInputState extends State<ChatInput> {
|
||||
_textController = InputTextFieldController();
|
||||
_inputFocusNode = FocusNode(
|
||||
onKeyEvent: (node, event) {
|
||||
// TODO(lucas): support mobile
|
||||
if (PlatformExtension.isDesktop) {
|
||||
if (_inputActionControl.canHandleKeyEvent(event)) {
|
||||
_inputActionControl.handleKeyEvent(event);
|
||||
@ -78,11 +82,16 @@ class _ChatInputState extends State<ChatInput> {
|
||||
},
|
||||
);
|
||||
|
||||
_inputFocusNode.addListener(() {
|
||||
setState(() {});
|
||||
});
|
||||
|
||||
_inputActionControl = ChatInputActionControl(
|
||||
chatId: widget.chatId,
|
||||
textController: _textController,
|
||||
textFieldFocusNode: _inputFocusNode,
|
||||
);
|
||||
_inputFocusNode.requestFocus();
|
||||
_handleSendButtonVisibilityModeChange();
|
||||
}
|
||||
|
||||
@ -96,36 +105,46 @@ class _ChatInputState extends State<ChatInput> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const textPadding = EdgeInsets.symmetric(horizontal: 16);
|
||||
const buttonPadding = EdgeInsets.symmetric(horizontal: 2);
|
||||
const inputPadding = EdgeInsets.all(6);
|
||||
final textPadding = isMobile
|
||||
? const EdgeInsets.only(left: 8.0, right: 4.0)
|
||||
: const EdgeInsets.symmetric(horizontal: 16);
|
||||
final borderRadius = BorderRadius.circular(isMobile ? 10 : 30);
|
||||
final color = isMobile
|
||||
? Colors.transparent
|
||||
: Theme.of(context).colorScheme.surfaceContainerHighest;
|
||||
|
||||
return Focus(
|
||||
child: Padding(
|
||||
padding: inputPadding,
|
||||
return Padding(
|
||||
padding: inputPadding,
|
||||
// ignore: use_decorated_box
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: _inputFocusNode.hasFocus && !isMobile
|
||||
? Theme.of(context).colorScheme.primary.withOpacity(0.6)
|
||||
: Colors.transparent,
|
||||
),
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
color: isMobile
|
||||
? Theme.of(context).colorScheme.surfaceContainer
|
||||
: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
elevation: 0.6,
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.onAttachmentPressed != null)
|
||||
AttachmentButton(
|
||||
isLoading: widget.isAttachmentUploading ?? false,
|
||||
onPressed: widget.onAttachmentPressed,
|
||||
padding: buttonPadding,
|
||||
),
|
||||
Expanded(child: _inputTextField(textPadding)),
|
||||
borderRadius: borderRadius,
|
||||
color: color,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
// TODO(lucas): support mobile
|
||||
if (PlatformExtension.isDesktop &&
|
||||
widget.aiType == const AIType.localAI())
|
||||
_attachmentButton(buttonPadding),
|
||||
Expanded(child: _inputTextField(context, textPadding)),
|
||||
|
||||
// TODO(lucas): support mobile
|
||||
if (PlatformExtension.isDesktop &&
|
||||
widget.aiType == const AIType.appflowyAI())
|
||||
_atButton(buttonPadding),
|
||||
_sendButton(buttonPadding),
|
||||
const HSpace(14),
|
||||
],
|
||||
if (widget.aiType == const AIType.appflowyAI())
|
||||
_atButton(buttonPadding),
|
||||
_sendButton(buttonPadding),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -144,7 +163,7 @@ class _ChatInputState extends State<ChatInput> {
|
||||
if (trimmedText != '') {
|
||||
final partialText = types.PartialText(
|
||||
text: trimmedText,
|
||||
metadata: _inputActionControl.metaData,
|
||||
metadata: _inputActionControl.consumeMetaData(),
|
||||
);
|
||||
widget.onSendPressed(partialText);
|
||||
_textController.clear();
|
||||
@ -160,33 +179,24 @@ class _ChatInputState extends State<ChatInput> {
|
||||
});
|
||||
}
|
||||
|
||||
Widget _inputTextField(EdgeInsets textPadding) {
|
||||
Widget _inputTextField(BuildContext context, EdgeInsets textPadding) {
|
||||
return CompositedTransformTarget(
|
||||
link: _layerLink,
|
||||
child: Padding(
|
||||
padding: textPadding,
|
||||
child: ExtendedTextField(
|
||||
key: _textFieldKey,
|
||||
specialTextSpanBuilder:
|
||||
ChatInputTextSpanBuilder(inputActionControl: _inputActionControl),
|
||||
controller: _textController,
|
||||
focusNode: _inputFocusNode,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: widget.hintText,
|
||||
focusedBorder: InputBorder.none,
|
||||
hintStyle: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 15,
|
||||
),
|
||||
decoration: _buildInputDecoration(context),
|
||||
keyboardType: TextInputType.multiline,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
minLines: 1,
|
||||
maxLines: 10,
|
||||
style: _buildTextStyle(context),
|
||||
specialTextSpanBuilder: ChatInputTextSpanBuilder(
|
||||
inputActionControl: _inputActionControl,
|
||||
),
|
||||
onChanged: (text) {
|
||||
_handleOnTextChange(context, text);
|
||||
},
|
||||
@ -195,47 +205,141 @@ class _ChatInputState extends State<ChatInput> {
|
||||
);
|
||||
}
|
||||
|
||||
void _handleOnTextChange(BuildContext context, String text) {
|
||||
InputDecoration _buildInputDecoration(BuildContext context) {
|
||||
if (!isMobile) {
|
||||
return InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: widget.hintText,
|
||||
focusedBorder: InputBorder.none,
|
||||
hintStyle: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor.withOpacity(0.5),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final borderRadius = BorderRadius.circular(10);
|
||||
return InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
hintText: widget.hintText,
|
||||
hintStyle: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor.withOpacity(0.5),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: borderRadius,
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: borderRadius,
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 1.2,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle? _buildTextStyle(BuildContext context) {
|
||||
if (!isMobile) {
|
||||
return TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 15,
|
||||
);
|
||||
}
|
||||
|
||||
return Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontSize: 15,
|
||||
height: 1.2,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleOnTextChange(BuildContext context, String text) async {
|
||||
if (widget.aiType != const AIType.appflowyAI()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_inputActionControl.onTextChanged(text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (PlatformExtension.isDesktop) {
|
||||
if (_inputActionControl.onTextChanged(text)) {
|
||||
ChatActionsMenu(
|
||||
anchor: ChatInputAnchor(
|
||||
anchorKey: _textFieldKey,
|
||||
layerLink: _layerLink,
|
||||
),
|
||||
handler: _inputActionControl,
|
||||
context: context,
|
||||
style: Theme.of(context).brightness == Brightness.dark
|
||||
? const ChatActionsMenuStyle.dark()
|
||||
: const ChatActionsMenuStyle.light(),
|
||||
).show();
|
||||
}
|
||||
ChatActionsMenu(
|
||||
anchor: ChatInputAnchor(
|
||||
anchorKey: _textFieldKey,
|
||||
layerLink: _layerLink,
|
||||
),
|
||||
handler: _inputActionControl,
|
||||
context: context,
|
||||
style: Theme.of(context).brightness == Brightness.dark
|
||||
? const ChatActionsMenuStyle.dark()
|
||||
: const ChatActionsMenuStyle.light(),
|
||||
).show();
|
||||
} else {
|
||||
// TODO(lucas): support mobile
|
||||
// if the focus node is on focus, unfocus it for better animation
|
||||
// otherwise, the page sheet animation will be blocked by the keyboard
|
||||
if (_inputFocusNode.hasFocus) {
|
||||
_inputFocusNode.unfocus();
|
||||
Future.delayed(const Duration(milliseconds: 100), () async {
|
||||
await _referPage(_inputActionControl);
|
||||
});
|
||||
} else {
|
||||
await _referPage(_inputActionControl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget _sendButton(EdgeInsets buttonPadding) {
|
||||
return Padding(
|
||||
padding: buttonPadding,
|
||||
child: ChatInputSendButton(
|
||||
onSendPressed: () {
|
||||
if (!_sendButtonEnabled) {
|
||||
return;
|
||||
}
|
||||
child: SizedBox.square(
|
||||
dimension: 26,
|
||||
child: ChatInputSendButton(
|
||||
onSendPressed: () {
|
||||
if (!_sendButtonEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!widget.isStreaming) {
|
||||
widget.onStopStreaming();
|
||||
_handleSendPressed();
|
||||
}
|
||||
},
|
||||
onStopStreaming: () => widget.onStopStreaming(),
|
||||
isStreaming: widget.isStreaming,
|
||||
enabled: _sendButtonEnabled,
|
||||
if (!widget.isStreaming) {
|
||||
widget.onStopStreaming();
|
||||
_handleSendPressed();
|
||||
}
|
||||
},
|
||||
onStopStreaming: () => widget.onStopStreaming(),
|
||||
isStreaming: widget.isStreaming,
|
||||
enabled: _sendButtonEnabled,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _attachmentButton(EdgeInsets buttonPadding) {
|
||||
return Padding(
|
||||
padding: buttonPadding,
|
||||
child: SizedBox.square(
|
||||
dimension: 26,
|
||||
child: ChatInputAttachment(
|
||||
onTap: () async {
|
||||
final path = await getIt<FilePickerService>().pickFiles(
|
||||
dialogTitle: '',
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ["pdf"],
|
||||
);
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final file in path.files) {
|
||||
if (file.path != null) {
|
||||
if (mounted) {
|
||||
context
|
||||
.read<ChatFileBloc>()
|
||||
.add(ChatFileEvent.newFile(file.path!, file.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -243,16 +347,39 @@ class _ChatInputState extends State<ChatInput> {
|
||||
Widget _atButton(EdgeInsets buttonPadding) {
|
||||
return Padding(
|
||||
padding: buttonPadding,
|
||||
child: ChatInputAtButton(
|
||||
onTap: () {
|
||||
_textController.text += '@';
|
||||
_inputFocusNode.requestFocus();
|
||||
_handleOnTextChange(context, _textController.text);
|
||||
},
|
||||
child: SizedBox.square(
|
||||
dimension: 26,
|
||||
child: ChatInputAtButton(
|
||||
onTap: () {
|
||||
_textController.text += '@';
|
||||
if (!isMobile) {
|
||||
_inputFocusNode.requestFocus();
|
||||
}
|
||||
_handleOnTextChange(context, _textController.text);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _referPage(ChatActionHandler handler) async {
|
||||
handler.onEnter();
|
||||
final selectedView = await showPageSelectorSheet(
|
||||
context,
|
||||
filter: (view) =>
|
||||
view.layout.isDocumentView &&
|
||||
!view.isSpace &&
|
||||
view.parentViewId.isNotEmpty,
|
||||
);
|
||||
if (selectedView == null) {
|
||||
handler.onExit();
|
||||
return;
|
||||
}
|
||||
handler.onSelected(ViewActionPage(view: selectedView));
|
||||
handler.onExit();
|
||||
_inputFocusNode.requestFocus();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant ChatInput oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
@ -38,7 +38,7 @@ class ChatInputSendButton extends StatelessWidget {
|
||||
radius: BorderRadius.circular(18),
|
||||
icon: FlowySvg(
|
||||
FlowySvgs.send_s,
|
||||
size: const Size.square(20),
|
||||
size: const Size.square(14),
|
||||
color: enabled ? Theme.of(context).colorScheme.primary : null,
|
||||
),
|
||||
onPressed: onSendPressed,
|
||||
|
@ -49,8 +49,9 @@ class ChatAIMessageBubble extends StatelessWidget {
|
||||
children: [
|
||||
const ChatBorderedCircleAvatar(
|
||||
child: FlowySvg(
|
||||
FlowySvgs.flowy_ai_chat_logo_s,
|
||||
size: Size.square(24),
|
||||
FlowySvgs.flowy_logo_s,
|
||||
size: Size.square(20),
|
||||
blendMode: null,
|
||||
),
|
||||
),
|
||||
Expanded(child: widget),
|
||||
@ -177,10 +178,9 @@ class CopyButton extends StatelessWidget {
|
||||
width: 24,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
fillColor: Theme.of(context).cardColor,
|
||||
icon: FlowySvg(
|
||||
FlowySvgs.ai_copy_s,
|
||||
size: const Size.square(14),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
icon: const FlowySvg(
|
||||
FlowySvgs.copy_s,
|
||||
size: Size.square(20),
|
||||
),
|
||||
onPressed: () async {
|
||||
final document = customMarkdownToDocument(textMessage.text);
|
@ -44,13 +44,14 @@ class AIMessageMetadata extends StatelessWidget {
|
||||
),
|
||||
useIntrinsicWidth: true,
|
||||
radius: BorderRadius.circular(6),
|
||||
text: FlowyText(
|
||||
m.source,
|
||||
fontSize: 14,
|
||||
text: Opacity(
|
||||
opacity: 0.5,
|
||||
child: FlowyText(
|
||||
m.name,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
onSelectedMetadata(m);
|
||||
},
|
||||
onTap: () => onSelectedMetadata(m),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -0,0 +1,211 @@
|
||||
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_member_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_avatar.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_input/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';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_chat_types/flutter_chat_types.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
const _leftPadding = 16.0;
|
||||
|
||||
class OtherUserMessageBubble extends StatelessWidget {
|
||||
const OtherUserMessageBubble({
|
||||
super.key,
|
||||
required this.message,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final Message message;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const padding = EdgeInsets.symmetric(horizontal: _leftPadding);
|
||||
final childWithPadding = Padding(padding: padding, child: child);
|
||||
final widget = isMobile
|
||||
? _wrapPopMenu(childWithPadding)
|
||||
: _wrapHover(childWithPadding);
|
||||
|
||||
if (context.read<ChatMemberBloc>().state.members[message.author.id] ==
|
||||
null) {
|
||||
context
|
||||
.read<ChatMemberBloc>()
|
||||
.add(ChatMemberEvent.getMemberInfo(message.author.id));
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BlocConsumer<ChatMemberBloc, ChatMemberState>(
|
||||
listenWhen: (previous, current) {
|
||||
return previous.members[message.author.id] !=
|
||||
current.members[message.author.id];
|
||||
},
|
||||
listener: (context, state) {},
|
||||
builder: (context, state) {
|
||||
final member = state.members[message.author.id];
|
||||
return ChatUserAvatar(
|
||||
iconUrl: member?.info.avatarUrl ?? "",
|
||||
name: member?.info.name ?? "",
|
||||
defaultName: "",
|
||||
);
|
||||
},
|
||||
),
|
||||
Expanded(child: widget),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
OtherUserMessageHover _wrapHover(Padding child) {
|
||||
return OtherUserMessageHover(
|
||||
message: message,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
ChatPopupMenu _wrapPopMenu(Padding childWithPadding) {
|
||||
return ChatPopupMenu(
|
||||
onAction: (action) {
|
||||
if (action == ChatMessageAction.copy && message is TextMessage) {
|
||||
Clipboard.setData(ClipboardData(text: (message as TextMessage).text));
|
||||
showMessageToast(LocaleKeys.grid_row_copyProperty.tr());
|
||||
}
|
||||
},
|
||||
builder: (context) => childWithPadding,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OtherUserMessageHover extends StatefulWidget {
|
||||
const OtherUserMessageHover({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.message,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final Message message;
|
||||
final bool autoShowHover = true;
|
||||
|
||||
@override
|
||||
State<OtherUserMessageHover> createState() => _OtherUserMessageHoverState();
|
||||
}
|
||||
|
||||
class _OtherUserMessageHoverState extends State<OtherUserMessageHover> {
|
||||
bool _isHover = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_isHover = widget.autoShowHover ? false : true;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> children = [
|
||||
DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: Corners.s6Border,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 30),
|
||||
child: widget.child,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
if (_isHover) {
|
||||
children.addAll(_buildOnHoverItems());
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
opaque: false,
|
||||
onEnter: (p) => setState(() {
|
||||
if (widget.autoShowHover) {
|
||||
_isHover = true;
|
||||
}
|
||||
}),
|
||||
onExit: (p) => setState(() {
|
||||
if (widget.autoShowHover) {
|
||||
_isHover = false;
|
||||
}
|
||||
}),
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildOnHoverItems() {
|
||||
final List<Widget> children = [];
|
||||
if (widget.message is TextMessage) {
|
||||
children.add(
|
||||
CopyButton(
|
||||
textMessage: widget.message as TextMessage,
|
||||
).positioned(left: _leftPadding, bottom: 0),
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
class CopyButton extends StatelessWidget {
|
||||
const CopyButton({
|
||||
super.key,
|
||||
required this.textMessage,
|
||||
});
|
||||
final TextMessage textMessage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyTooltip(
|
||||
message: LocaleKeys.settings_menu_clickToCopy.tr(),
|
||||
child: FlowyIconButton(
|
||||
width: 24,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
fillColor: Theme.of(context).cardColor,
|
||||
icon: FlowySvg(
|
||||
FlowySvgs.ai_copy_s,
|
||||
size: const Size.square(14),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
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,6 +1,6 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_user_message_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_member_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_avatar.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
@ -27,49 +27,52 @@ class ChatUserMessageBubble extends StatelessWidget {
|
||||
const borderRadius = BorderRadius.all(Radius.circular(6));
|
||||
final backgroundColor =
|
||||
Theme.of(context).colorScheme.surfaceContainerHighest;
|
||||
if (context.read<ChatMemberBloc>().state.members[message.author.id] ==
|
||||
null) {
|
||||
context
|
||||
.read<ChatMemberBloc>()
|
||||
.add(ChatMemberEvent.getMemberInfo(message.author.id));
|
||||
}
|
||||
|
||||
return BlocProvider(
|
||||
create: (context) => ChatUserMessageBloc(message: message)
|
||||
..add(const ChatUserMessageEvent.initial()),
|
||||
child: BlocBuilder<ChatUserMessageBloc, ChatUserMessageState>(
|
||||
builder: (context, state) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// _wrapHover(
|
||||
Flexible(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: borderRadius,
|
||||
color: backgroundColor,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
child: child,
|
||||
return BlocConsumer<ChatMemberBloc, ChatMemberState>(
|
||||
listenWhen: (previous, current) {
|
||||
return previous.members[message.author.id] !=
|
||||
current.members[message.author.id];
|
||||
},
|
||||
listener: (context, state) {},
|
||||
builder: (context, state) {
|
||||
final member = state.members[message.author.id];
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// _wrapHover(
|
||||
Flexible(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: borderRadius,
|
||||
color: backgroundColor,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
// ),
|
||||
BlocBuilder<ChatUserMessageBloc, ChatUserMessageState>(
|
||||
builder: (context, state) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ChatUserAvatar(
|
||||
iconUrl: state.member?.avatarUrl ?? "",
|
||||
name: state.member?.name ?? "",
|
||||
size: 36,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// ),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ChatUserAvatar(
|
||||
iconUrl: member?.info.avatarUrl ?? "",
|
||||
name: member?.info.name ?? "",
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker_header.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/emoji_skin_tone.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/emoji_search_bar.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -20,7 +21,7 @@ class FlowyEmojiHeader extends StatelessWidget {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: FlowyText.regular(
|
||||
category.id,
|
||||
category.id.capitalize(),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
|
@ -8,6 +8,7 @@ import 'package:appflowy/plugins/database/application/row/row_cache.dart';
|
||||
import 'package:appflowy/plugins/database/application/row/row_controller.dart';
|
||||
import 'package:appflowy/plugins/database/board/application/board_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/card_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
|
||||
@ -64,7 +65,10 @@ class HiddenGroupsColumn extends StatelessWidget {
|
||||
height: 50,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 80 + margin.left,
|
||||
left: margin.left +
|
||||
context
|
||||
.read<DatabasePluginWidgetBuilderSize>()
|
||||
.horizontalPadding,
|
||||
right: margin.right + 4,
|
||||
),
|
||||
child: Row(
|
||||
|
@ -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/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
@ -20,12 +18,12 @@ import 'package:easy_localization/easy_localization.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:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../application/row/row_controller.dart';
|
||||
import '../../widgets/row/row_detail.dart';
|
||||
|
||||
import 'calendar_day.dart';
|
||||
import 'layout/sizes.dart';
|
||||
import 'toolbar/calendar_setting_bar.dart';
|
||||
@ -185,11 +183,17 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
return LayoutBuilder(
|
||||
// must specify MonthView width for useAvailableVerticalSpace to work properly
|
||||
builder: (context, constraints) {
|
||||
EdgeInsets padding = PlatformExtension.isMobile
|
||||
? CalendarSize.contentInsetsMobile
|
||||
: CalendarSize.contentInsets +
|
||||
const EdgeInsets.symmetric(horizontal: 40);
|
||||
final double horizontalPadding =
|
||||
context.read<DatabasePluginWidgetBuilderSize>().horizontalPadding;
|
||||
if (horizontalPadding == 0) {
|
||||
padding = padding.copyWith(left: 0, right: 0);
|
||||
}
|
||||
return Padding(
|
||||
padding: PlatformExtension.isMobile
|
||||
? CalendarSize.contentInsetsMobile
|
||||
: CalendarSize.contentInsets +
|
||||
const EdgeInsets.symmetric(horizontal: 40),
|
||||
padding: padding,
|
||||
child: ScrollConfiguration(
|
||||
behavior:
|
||||
ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
||||
|
@ -1,9 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/plugins/database/grid/application/calculations/calculations_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/application/grid_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart';
|
||||
import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class GridCalculationsRow extends StatelessWidget {
|
||||
@ -27,9 +26,12 @@ class GridCalculationsRow extends StatelessWidget {
|
||||
)..add(const CalculationsEvent.started()),
|
||||
child: BlocBuilder<CalculationsBloc, CalculationsState>(
|
||||
builder: (context, state) {
|
||||
final padding =
|
||||
context.read<DatabasePluginWidgetBuilderSize>().horizontalPadding;
|
||||
return Padding(
|
||||
padding:
|
||||
includeDefaultInsets ? GridSize.contentInsets : EdgeInsets.zero,
|
||||
padding: includeDefaultInsets
|
||||
? EdgeInsets.symmetric(horizontal: padding)
|
||||
: EdgeInsets.zero,
|
||||
child: Row(
|
||||
children: [
|
||||
...state.fields.map(
|
||||
|
@ -1,13 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/grid/application/grid_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.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:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class GridAddRowButton extends StatelessWidget {
|
||||
@ -41,8 +41,11 @@ class GridRowBottomBar extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final padding =
|
||||
context.read<DatabasePluginWidgetBuilderSize>().horizontalPadding;
|
||||
return Container(
|
||||
padding: GridSize.footerContentInsets + const EdgeInsets.only(left: 40),
|
||||
padding: GridSize.footerContentInsets.copyWith(left: 0) +
|
||||
EdgeInsets.only(left: padding),
|
||||
height: GridSize.footerHeight,
|
||||
child: const GridAddRowButton(),
|
||||
);
|
||||
|
@ -98,7 +98,7 @@ class _RowLeadingState extends State<_RowLeading> {
|
||||
return AppFlowyPopover(
|
||||
controller: popoverController,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
constraints: BoxConstraints.loose(const Size(176, 200)),
|
||||
constraints: BoxConstraints.loose(const Size(200, 200)),
|
||||
direction: PopoverDirection.rightWithCenterAligned,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 8),
|
||||
popupBuilder: (_) {
|
||||
|
@ -56,7 +56,8 @@ class _DatabaseViewWidgetState extends State<DatabaseViewWidget> {
|
||||
shrinkWrap: widget.shrinkWrap,
|
||||
context: PluginContext(),
|
||||
data: {
|
||||
kDatabasePluginWidgetBuilderHorizontalPadding: 40.0,
|
||||
kDatabasePluginWidgetBuilderHorizontalPadding:
|
||||
view.layout == ViewLayoutPB.Grid ? 40.0 : 0.0,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -130,8 +130,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
final List<ToolbarItem> toolbarItems = [
|
||||
smartEditItem..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable,
|
||||
paragraphItem..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable,
|
||||
...headingItems
|
||||
..forEach((e) => e.isActive = onlyShowInSingleSelectionAndTextType),
|
||||
headingsToolbarItem
|
||||
..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable,
|
||||
...markdownFormatItems..forEach((e) => e.isActive = showInAnyTextType),
|
||||
quoteItem..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable,
|
||||
bulletedListItem
|
||||
@ -387,6 +387,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
editorState: editorState,
|
||||
editorScrollController: editorScrollController,
|
||||
textDirection: textDirection,
|
||||
tooltipBuilder: (context, id, message, child) => widget.styleCustomizer
|
||||
.buildToolbarItemTooltip(context, id, message, child,),
|
||||
child: editor,
|
||||
),
|
||||
);
|
||||
|
@ -9,12 +9,13 @@ import 'package:flutter/material.dart';
|
||||
const String leftAlignmentKey = 'left';
|
||||
const String centerAlignmentKey = 'center';
|
||||
const String rightAlignmentKey = 'right';
|
||||
const String kAlignToolbarItemId = 'editor.align';
|
||||
|
||||
final alignToolbarItem = ToolbarItem(
|
||||
id: 'editor.align',
|
||||
id: kAlignToolbarItemId,
|
||||
group: 4,
|
||||
isActive: onlyShowInTextType,
|
||||
builder: (context, editorState, highlightColor, _) {
|
||||
builder: (context, editorState, highlightColor, _, tooltipBuilder) {
|
||||
final selection = editorState.selection!;
|
||||
final nodes = editorState.getNodesInSelection(selection);
|
||||
|
||||
@ -37,35 +38,37 @@ final alignToolbarItem = ToolbarItem(
|
||||
data = FlowySvgs.toolbar_align_right_s;
|
||||
}
|
||||
|
||||
final child = FlowySvg(
|
||||
Widget child = FlowySvg(
|
||||
data,
|
||||
size: const Size.square(16),
|
||||
color: isHighlight ? highlightColor : Colors.white,
|
||||
);
|
||||
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: FlowyTooltip(
|
||||
message: LocaleKeys.document_plugins_optionAction_align.tr(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: _AlignmentButtons(
|
||||
child: child,
|
||||
onAlignChanged: (align) async {
|
||||
await editorState.updateNode(
|
||||
selection,
|
||||
(node) => node.copyWith(
|
||||
attributes: {
|
||||
...node.attributes,
|
||||
blockComponentAlign: align,
|
||||
},
|
||||
),
|
||||
);
|
||||
child = _AlignmentButtons(
|
||||
child: child,
|
||||
onAlignChanged: (align) async {
|
||||
await editorState.updateNode(
|
||||
selection,
|
||||
(node) => node.copyWith(
|
||||
attributes: {
|
||||
...node.attributes,
|
||||
blockComponentAlign: align,
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (tooltipBuilder != null) {
|
||||
child = tooltipBuilder(
|
||||
context,
|
||||
kAlignToolbarItemId,
|
||||
LocaleKeys.document_plugins_optionAction_align.tr(),
|
||||
child,
|
||||
);
|
||||
}
|
||||
|
||||
return child;
|
||||
},
|
||||
);
|
||||
|
||||
@ -83,15 +86,17 @@ class _AlignmentButtons extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AlignmentButtonsState extends State<_AlignmentButtons> {
|
||||
final controller = PopoverController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppFlowyPopover(
|
||||
windowPadding: const EdgeInsets.all(0),
|
||||
margin: const EdgeInsets.all(4),
|
||||
margin: const EdgeInsets.symmetric(vertical: 2.0),
|
||||
direction: PopoverDirection.bottomWithCenterAligned,
|
||||
offset: const Offset(0, 10),
|
||||
decorationColor: Theme.of(context).colorScheme.onTertiary,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
popupBuilder: (_) {
|
||||
keepEditorFocusNotifier.increase();
|
||||
return _AlignButtons(onAlignChanged: widget.onAlignChanged);
|
||||
@ -99,7 +104,12 @@ class _AlignmentButtonsState extends State<_AlignmentButtons> {
|
||||
onClose: () {
|
||||
keepEditorFocusNotifier.decrease();
|
||||
},
|
||||
child: widget.child,
|
||||
child: FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text: widget.child,
|
||||
hoverColor: Colors.grey.withOpacity(0.3),
|
||||
onTap: () => controller.show(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -114,7 +124,7 @@ class _AlignButtons extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 32,
|
||||
height: 28,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -156,17 +166,16 @@ class _AlignButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: FlowyTooltip(
|
||||
message: tooltips,
|
||||
child: FlowySvg(
|
||||
icon,
|
||||
size: const Size.square(16),
|
||||
color: Colors.white,
|
||||
),
|
||||
return FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
hoverColor: Colors.grey.withOpacity(0.3),
|
||||
onTap: onTap,
|
||||
text: FlowyTooltip(
|
||||
message: tooltips,
|
||||
child: FlowySvg(
|
||||
icon,
|
||||
size: const Size.square(16),
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -179,7 +188,7 @@ class _Divider extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Container(
|
||||
width: 1,
|
||||
color: Colors.grey,
|
||||
|
@ -1,5 +1,6 @@
|
||||
extension Capitalize on String {
|
||||
String capitalize() {
|
||||
if (isEmpty) return this;
|
||||
return "${this[0].toUpperCase()}${substring(1)}";
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,3 @@
|
||||
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
||||
@ -9,6 +6,7 @@ import 'package:appflowy/util/font_family_extension.dart';
|
||||
import 'package:appflowy/util/levenshtein.dart';
|
||||
import 'package:appflowy/workspace/application/appearance_defaults.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/setting_list_tile.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/setting_value_dropdown.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
@ -20,56 +18,66 @@ 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/style_widget/text_field.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
const kFontToolbarItemId = 'editor.font';
|
||||
|
||||
final customizeFontToolbarItem = ToolbarItem(
|
||||
id: 'editor.font',
|
||||
id: kFontToolbarItemId,
|
||||
group: 4,
|
||||
isActive: onlyShowInTextType,
|
||||
builder: (context, editorState, highlightColor, _) {
|
||||
builder: (context, editorState, highlightColor, _, tooltipBuilder) {
|
||||
final selection = editorState.selection!;
|
||||
final popoverController = PopoverController();
|
||||
final String? currentFontFamily = editorState
|
||||
.getDeltaAttributeValueInSelection(AppFlowyRichTextKeys.fontFamily);
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: FontFamilyDropDown(
|
||||
currentFontFamily: currentFontFamily ?? '',
|
||||
offset: const Offset(0, 12),
|
||||
popoverController: popoverController,
|
||||
onOpen: () => keepEditorFocusNotifier.increase(),
|
||||
onClose: () => keepEditorFocusNotifier.decrease(),
|
||||
showResetButton: true,
|
||||
onFontFamilyChanged: (fontFamily) async {
|
||||
popoverController.close();
|
||||
try {
|
||||
await editorState.formatDelta(selection, {
|
||||
AppFlowyRichTextKeys.fontFamily: fontFamily,
|
||||
});
|
||||
} catch (e) {
|
||||
Log.error('Failed to set font family: $e');
|
||||
}
|
||||
},
|
||||
onResetFont: () async {
|
||||
popoverController.close();
|
||||
await editorState
|
||||
.formatDelta(selection, {AppFlowyRichTextKeys.fontFamily: null});
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: FlowyTooltip(
|
||||
message: LocaleKeys.document_plugins_fonts.tr(),
|
||||
child: const FlowySvg(
|
||||
FlowySvgs.font_family_s,
|
||||
size: Size.square(16.0),
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
|
||||
Widget child = FontFamilyDropDown(
|
||||
currentFontFamily: currentFontFamily ?? '',
|
||||
offset: const Offset(0, 12),
|
||||
popoverController: popoverController,
|
||||
onOpen: () => keepEditorFocusNotifier.increase(),
|
||||
onClose: () => keepEditorFocusNotifier.decrease(),
|
||||
showResetButton: true,
|
||||
onFontFamilyChanged: (fontFamily) async {
|
||||
popoverController.close();
|
||||
try {
|
||||
await editorState.formatDelta(selection, {
|
||||
AppFlowyRichTextKeys.fontFamily: fontFamily,
|
||||
});
|
||||
} catch (e) {
|
||||
Log.error('Failed to set font family: $e');
|
||||
}
|
||||
},
|
||||
onResetFont: () async {
|
||||
popoverController.close();
|
||||
await editorState
|
||||
.formatDelta(selection, {AppFlowyRichTextKeys.fontFamily: null});
|
||||
},
|
||||
child: FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
hoverColor: Colors.grey.withOpacity(0.3),
|
||||
onTap: () => popoverController.show(),
|
||||
text: const FlowySvg(
|
||||
FlowySvgs.font_family_s,
|
||||
size: Size.square(16.0),
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (tooltipBuilder != null) {
|
||||
child = tooltipBuilder(
|
||||
context,
|
||||
kFontToolbarItemId,
|
||||
LocaleKeys.document_plugins_fonts.tr(),
|
||||
child,
|
||||
);
|
||||
}
|
||||
|
||||
return child;
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -0,0 +1,214 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final _headingData = [
|
||||
(FlowySvgs.h1_s, LocaleKeys.editor_heading1.tr()),
|
||||
(FlowySvgs.h2_s, LocaleKeys.editor_heading2.tr()),
|
||||
(FlowySvgs.h3_s, LocaleKeys.editor_heading3.tr()),
|
||||
];
|
||||
|
||||
final headingsToolbarItem = ToolbarItem(
|
||||
id: 'editor.headings',
|
||||
group: 1,
|
||||
isActive: onlyShowInTextType,
|
||||
builder: (context, editorState, highlightColor, _, __) {
|
||||
final selection = editorState.selection!;
|
||||
final node = editorState.getNodeAtPath(selection.start.path)!;
|
||||
final delta = (node.delta ?? Delta()).toJson();
|
||||
int level = node.attributes[HeadingBlockKeys.level] ?? 1;
|
||||
final originLevel = level;
|
||||
final isHighlight =
|
||||
node.type == HeadingBlockKeys.type && (level >= 1 && level <= 3);
|
||||
// only supports the level 1 - 3 in the toolbar, ignore the other levels
|
||||
level = level.clamp(1, 3);
|
||||
|
||||
final svg = _headingData[level - 1].$1;
|
||||
final message = _headingData[level - 1].$2;
|
||||
|
||||
final child = FlowyTooltip(
|
||||
message: message,
|
||||
preferBelow: false,
|
||||
child: Row(
|
||||
children: [
|
||||
FlowySvg(
|
||||
svg,
|
||||
size: const Size.square(18),
|
||||
color: isHighlight ? highlightColor : Colors.white,
|
||||
),
|
||||
const HSpace(2.0),
|
||||
const FlowySvg(
|
||||
FlowySvgs.arrow_down_s,
|
||||
size: Size.square(12),
|
||||
color: Colors.grey,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return _HeadingPopup(
|
||||
currentLevel: isHighlight ? level : -1,
|
||||
highlightColor: highlightColor,
|
||||
child: child,
|
||||
onLevelChanged: (newLevel) async {
|
||||
// same level means cancel the heading
|
||||
final type =
|
||||
newLevel == originLevel && node.type == HeadingBlockKeys.type
|
||||
? ParagraphBlockKeys.type
|
||||
: HeadingBlockKeys.type;
|
||||
|
||||
await editorState.formatNode(
|
||||
selection,
|
||||
(node) => node.copyWith(
|
||||
type: type,
|
||||
attributes: {
|
||||
HeadingBlockKeys.level: newLevel,
|
||||
blockComponentBackgroundColor:
|
||||
node.attributes[blockComponentBackgroundColor],
|
||||
blockComponentTextDirection:
|
||||
node.attributes[blockComponentTextDirection],
|
||||
blockComponentDelta: delta,
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
class _HeadingPopup extends StatelessWidget {
|
||||
const _HeadingPopup({
|
||||
required this.currentLevel,
|
||||
required this.highlightColor,
|
||||
required this.onLevelChanged,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final int currentLevel;
|
||||
final Color highlightColor;
|
||||
final Function(int level) onLevelChanged;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppFlowyPopover(
|
||||
windowPadding: const EdgeInsets.all(0),
|
||||
margin: const EdgeInsets.symmetric(vertical: 2.0),
|
||||
direction: PopoverDirection.bottomWithCenterAligned,
|
||||
offset: const Offset(0, 10),
|
||||
decorationColor: Theme.of(context).colorScheme.onTertiary,
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
popupBuilder: (_) {
|
||||
keepEditorFocusNotifier.increase();
|
||||
return _HeadingButtons(
|
||||
currentLevel: currentLevel,
|
||||
highlightColor: highlightColor,
|
||||
onLevelChanged: onLevelChanged,
|
||||
);
|
||||
},
|
||||
onClose: () {
|
||||
keepEditorFocusNotifier.decrease();
|
||||
},
|
||||
child: FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
hoverColor: Colors.grey.withOpacity(0.3),
|
||||
text: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HeadingButtons extends StatelessWidget {
|
||||
const _HeadingButtons({
|
||||
required this.highlightColor,
|
||||
required this.currentLevel,
|
||||
required this.onLevelChanged,
|
||||
});
|
||||
|
||||
final int currentLevel;
|
||||
final Color highlightColor;
|
||||
final Function(int level) onLevelChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 28,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const HSpace(4),
|
||||
..._headingData.mapIndexed((index, data) {
|
||||
final svg = data.$1;
|
||||
final message = data.$2;
|
||||
return [
|
||||
_HeadingButton(
|
||||
icon: svg,
|
||||
tooltip: message,
|
||||
onTap: () => onLevelChanged(index + 1),
|
||||
isHighlight: index + 1 == currentLevel,
|
||||
highlightColor: highlightColor,
|
||||
),
|
||||
index != _headingData.length - 1
|
||||
? const _Divider()
|
||||
: const SizedBox.shrink(),
|
||||
];
|
||||
}).flattened,
|
||||
const HSpace(4),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HeadingButton extends StatelessWidget {
|
||||
const _HeadingButton({
|
||||
required this.icon,
|
||||
required this.tooltip,
|
||||
required this.onTap,
|
||||
required this.highlightColor,
|
||||
required this.isHighlight,
|
||||
});
|
||||
|
||||
final Color highlightColor;
|
||||
final FlowySvgData icon;
|
||||
final String tooltip;
|
||||
final VoidCallback onTap;
|
||||
final bool isHighlight;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
hoverColor: Colors.grey.withOpacity(0.3),
|
||||
onTap: onTap,
|
||||
text: FlowyTooltip(
|
||||
message: tooltip,
|
||||
preferBelow: true,
|
||||
child: FlowySvg(
|
||||
icon,
|
||||
size: const Size.square(18),
|
||||
color: isHighlight ? highlightColor : Colors.white,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Divider extends StatelessWidget {
|
||||
const _Divider();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Container(
|
||||
width: 1,
|
||||
color: Colors.grey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -5,11 +5,13 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const _kInlineMathEquationToolbarItemId = 'editor.inline_math_equation';
|
||||
|
||||
final ToolbarItem inlineMathEquationItem = ToolbarItem(
|
||||
id: 'editor.inline_math_equation',
|
||||
id: _kInlineMathEquationToolbarItemId,
|
||||
group: 2,
|
||||
isActive: onlyShowInSingleSelectionAndTextType,
|
||||
builder: (context, editorState, highlightColor, _) {
|
||||
builder: (context, editorState, highlightColor, _, tooltipBuilder) {
|
||||
final selection = editorState.selection!;
|
||||
final nodes = editorState.getNodesInSelection(selection);
|
||||
final isHighlight = nodes.allSatisfyInSelection(selection, (delta) {
|
||||
@ -17,7 +19,7 @@ final ToolbarItem inlineMathEquationItem = ToolbarItem(
|
||||
(attributes) => attributes[InlineMathEquationKeys.formula] != null,
|
||||
);
|
||||
});
|
||||
return SVGIconItemWidget(
|
||||
final child = SVGIconItemWidget(
|
||||
iconBuilder: (_) => FlowySvg(
|
||||
FlowySvgs.math_lg,
|
||||
size: const Size.square(16),
|
||||
@ -25,7 +27,6 @@ final ToolbarItem inlineMathEquationItem = ToolbarItem(
|
||||
),
|
||||
isHighlight: isHighlight,
|
||||
highlightColor: highlightColor,
|
||||
tooltip: LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
onPressed: () async {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null || selection.isCollapsed) {
|
||||
@ -71,5 +72,16 @@ final ToolbarItem inlineMathEquationItem = ToolbarItem(
|
||||
await editorState.apply(transaction);
|
||||
},
|
||||
);
|
||||
|
||||
if (tooltipBuilder != null) {
|
||||
return tooltipBuilder(
|
||||
context,
|
||||
_kInlineMathEquationToolbarItemId,
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
child,
|
||||
);
|
||||
}
|
||||
|
||||
return child;
|
||||
},
|
||||
);
|
||||
|
@ -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/mobile/presentation/base/flowy_search_text_field.dart';
|
||||
@ -11,13 +9,17 @@ import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||
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';
|
||||
|
||||
Future<String?> showPageSelectorSheet(
|
||||
Future<ViewPB?> showPageSelectorSheet(
|
||||
BuildContext context, {
|
||||
String? currentViewId,
|
||||
String? selectedViewId,
|
||||
bool Function(ViewPB view)? filter,
|
||||
}) async {
|
||||
return showMobileBottomSheet<String>(
|
||||
filter ??= (v) => !v.isSpace && v.parentViewId.isNotEmpty;
|
||||
|
||||
return showMobileBottomSheet<ViewPB>(
|
||||
context,
|
||||
title: LocaleKeys.document_mobilePageSelector_title.tr(),
|
||||
showHeader: true,
|
||||
@ -32,16 +34,22 @@ Future<String?> showPageSelectorSheet(
|
||||
child: _MobilePageSelectorBody(
|
||||
currentViewId: currentViewId,
|
||||
selectedViewId: selectedViewId,
|
||||
filter: filter,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _MobilePageSelectorBody extends StatefulWidget {
|
||||
const _MobilePageSelectorBody({this.currentViewId, this.selectedViewId});
|
||||
const _MobilePageSelectorBody({
|
||||
this.currentViewId,
|
||||
this.selectedViewId,
|
||||
this.filter,
|
||||
});
|
||||
|
||||
final String? currentViewId;
|
||||
final String? selectedViewId;
|
||||
final bool Function(ViewPB view)? filter;
|
||||
|
||||
@override
|
||||
State<_MobilePageSelectorBody> createState() =>
|
||||
@ -79,7 +87,10 @@ class _MobilePageSelectorBodyState extends State<_MobilePageSelectorBody> {
|
||||
);
|
||||
}
|
||||
|
||||
final views = snapshot.data!;
|
||||
final views = snapshot.data!
|
||||
.where((v) => widget.filter?.call(v) ?? true)
|
||||
.toList();
|
||||
|
||||
if (widget.currentViewId != null) {
|
||||
views.removeWhere((v) => v.id == widget.currentViewId);
|
||||
}
|
||||
@ -118,7 +129,7 @@ class _MobilePageSelectorBodyState extends State<_MobilePageSelectorBody> {
|
||||
),
|
||||
text: view.name,
|
||||
isSelected: view.id == widget.selectedViewId,
|
||||
onTap: () => Navigator.of(context).pop(view.id),
|
||||
onTap: () => Navigator.of(context).pop(view),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
|
@ -1,7 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/type_option_menu_item.dart';
|
||||
@ -21,6 +19,7 @@ import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.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:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
final addBlockToolbarItem = AppFlowyMobileToolbarItem(
|
||||
@ -230,16 +229,16 @@ class _AddBlockMenu extends StatelessWidget {
|
||||
AppGlobals.rootNavKey.currentContext?.pop(true);
|
||||
|
||||
final currentViewId = getIt<MenuSharedState>().latestOpenView?.id;
|
||||
final viewId = await showPageSelectorSheet(
|
||||
final view = await showPageSelectorSheet(
|
||||
context,
|
||||
currentViewId: currentViewId,
|
||||
);
|
||||
|
||||
if (viewId != null) {
|
||||
if (view != null) {
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
editorState.insertBlockAfterCurrentSelection(
|
||||
selection,
|
||||
pageMentionNode(viewId),
|
||||
pageMentionNode(view.id),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -11,12 +11,15 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const _kSmartEditToolbarItemId = 'appflowy.editor.smart_edit';
|
||||
|
||||
final ToolbarItem smartEditItem = ToolbarItem(
|
||||
id: 'appflowy.editor.smart_edit',
|
||||
id: _kSmartEditToolbarItemId,
|
||||
group: 0,
|
||||
isActive: onlyShowInSingleSelectionAndTextType,
|
||||
builder: (context, editorState, _, __) => SmartEditActionList(
|
||||
builder: (context, editorState, _, __, tooltipBuilder) => SmartEditActionList(
|
||||
editorState: editorState,
|
||||
tooltipBuilder: tooltipBuilder,
|
||||
),
|
||||
);
|
||||
|
||||
@ -24,9 +27,11 @@ class SmartEditActionList extends StatefulWidget {
|
||||
const SmartEditActionList({
|
||||
super.key,
|
||||
required this.editorState,
|
||||
this.tooltipBuilder,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
final ToolbarTooltipBuilder? tooltipBuilder;
|
||||
|
||||
@override
|
||||
State<SmartEditActionList> createState() => _SmartEditActionListState();
|
||||
@ -60,11 +65,8 @@ class _SmartEditActionListState extends State<SmartEditActionList> {
|
||||
onClosed: () => keepEditorFocusNotifier.decrease(),
|
||||
buildChild: (controller) {
|
||||
keepEditorFocusNotifier.increase();
|
||||
return FlowyIconButton(
|
||||
final child = FlowyIconButton(
|
||||
hoverColor: Colors.transparent,
|
||||
tooltipText: isAIEnabled
|
||||
? LocaleKeys.document_plugins_smartEdit.tr()
|
||||
: LocaleKeys.document_plugins_appflowyAIEditDisabled.tr(),
|
||||
preferBelow: false,
|
||||
icon: const Icon(
|
||||
Icons.lightbulb_outline,
|
||||
@ -83,6 +85,19 @@ class _SmartEditActionListState extends State<SmartEditActionList> {
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (widget.tooltipBuilder != null) {
|
||||
return widget.tooltipBuilder!(
|
||||
context,
|
||||
_kSmartEditToolbarItemId,
|
||||
isAIEnabled
|
||||
? LocaleKeys.document_plugins_smartEdit.tr()
|
||||
: LocaleKeys.document_plugins_appflowyAIEditDisabled.tr(),
|
||||
child,
|
||||
);
|
||||
}
|
||||
|
||||
return child;
|
||||
},
|
||||
onSelected: (action, controller) {
|
||||
controller.close();
|
||||
|
@ -14,15 +14,17 @@ export 'database/inline_database_menu_item.dart';
|
||||
export 'database/referenced_database_menu_item.dart';
|
||||
export 'error/error_block_component_builder.dart';
|
||||
export 'extensions/flowy_tint_extension.dart';
|
||||
export 'file/file_block.dart';
|
||||
export 'find_and_replace/find_and_replace_menu.dart';
|
||||
export 'font/customize_font_toolbar_item.dart';
|
||||
export 'header/cover_editor_bloc.dart';
|
||||
export 'header/custom_cover_picker.dart';
|
||||
export 'header/document_header_node_widget.dart';
|
||||
export 'heading/heading_toolbar_item.dart';
|
||||
export 'image/custom_image_block_component/image_menu.dart';
|
||||
export 'image/multi_image_block_component/multi_image_menu.dart';
|
||||
export 'image/image_selection_menu.dart';
|
||||
export 'image/mobile_image_toolbar_item.dart';
|
||||
export 'image/multi_image_block_component/multi_image_menu.dart';
|
||||
export 'inline_math_equation/inline_math_equation.dart';
|
||||
export 'inline_math_equation/inline_math_equation_toolbar_item.dart';
|
||||
export 'link_preview/custom_link_preview.dart';
|
||||
@ -53,4 +55,3 @@ export 'table/table_option_action.dart';
|
||||
export 'todo_list/todo_list_icon.dart';
|
||||
export 'toggle/toggle_block_component.dart';
|
||||
export 'toggle/toggle_block_shortcut_event.dart';
|
||||
export 'file/file_block.dart';
|
||||
|
@ -1,4 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/font_colors.dart';
|
||||
@ -13,7 +16,10 @@ import 'package:appflowy/workspace/application/settings/appearance/appearance_cu
|
||||
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -100,7 +106,8 @@ class EditorStyleCustomizer {
|
||||
final theme = Theme.of(context);
|
||||
final fontSize = pageStyle.fontLayout.fontSize;
|
||||
final lineHeight = pageStyle.lineHeightLayout.lineHeight;
|
||||
final fontFamily = pageStyle.fontFamily ?? defaultFontFamily;
|
||||
final fontFamily = pageStyle.fontFamily ??
|
||||
context.read<AppearanceSettingsCubit>().state.font;
|
||||
final defaultTextDirection =
|
||||
context.read<DocumentAppearanceCubit>().state.defaultTextDirection;
|
||||
final textScaleFactor =
|
||||
@ -394,4 +401,83 @@ class EditorStyleCustomizer {
|
||||
after,
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildToolbarItemTooltip(
|
||||
BuildContext context,
|
||||
String id,
|
||||
String message,
|
||||
Widget child,
|
||||
) {
|
||||
final tooltipMessage = _buildTooltipMessage(id, message);
|
||||
child = FlowyTooltip(
|
||||
richMessage: tooltipMessage,
|
||||
preferBelow: false,
|
||||
verticalOffset: 20,
|
||||
child: child,
|
||||
);
|
||||
|
||||
// the align/font toolbar item doesn't need the hover effect
|
||||
final toolbarItemsWithoutHover = {
|
||||
kFontToolbarItemId,
|
||||
kAlignToolbarItemId,
|
||||
};
|
||||
|
||||
if (!toolbarItemsWithoutHover.contains(id)) {
|
||||
child = Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: FlowyHover(
|
||||
style: HoverStyle(
|
||||
hoverColor: Colors.grey.withOpacity(0.3),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
TextSpan _buildTooltipMessage(String id, String message) {
|
||||
final markdownItemTooltips = {
|
||||
'underline': (LocaleKeys.toolbar_underline.tr(), 'U'),
|
||||
'bold': (LocaleKeys.toolbar_bold.tr(), 'B'),
|
||||
'italic': (LocaleKeys.toolbar_italic.tr(), 'I'),
|
||||
'strikethrough': (LocaleKeys.toolbar_strike.tr(), 'Shift+S'),
|
||||
'code': (LocaleKeys.toolbar_inlineCode.tr(), 'E'),
|
||||
};
|
||||
|
||||
final markdownItemIds = markdownItemTooltips.keys.toSet();
|
||||
// the items without shortcuts
|
||||
if (!markdownItemIds.contains(id)) {
|
||||
return TextSpan(
|
||||
text: message,
|
||||
style: context.tooltipTextStyle(),
|
||||
);
|
||||
}
|
||||
|
||||
final tooltip = markdownItemTooltips[id];
|
||||
if (tooltip == null) {
|
||||
return TextSpan(
|
||||
text: message,
|
||||
style: context.tooltipTextStyle(),
|
||||
);
|
||||
}
|
||||
|
||||
final textSpan = TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${tooltip.$1}\n',
|
||||
style: context.tooltipTextStyle(),
|
||||
),
|
||||
TextSpan(
|
||||
text: (Platform.isMacOS ? '⌘+' : 'Ctrl+\\') + tooltip.$2,
|
||||
style: context
|
||||
.tooltipTextStyle()
|
||||
?.copyWith(color: Theme.of(context).hintColor),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return textSpan;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
@ -38,11 +37,7 @@ TextStyle getGoogleFontSafely(
|
||||
letterSpacing: letterSpacing,
|
||||
height: lineHeight,
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error(
|
||||
'Font family $fontFamily is not available, using default font family instead',
|
||||
);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
return TextStyle(
|
||||
|
@ -0,0 +1,26 @@
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
extension PickerColors on BuildContext {
|
||||
Color get pickerTextColor {
|
||||
return Theme.of(this).isLightMode
|
||||
? const Color(0x80171717)
|
||||
: Colors.white.withOpacity(0.5);
|
||||
}
|
||||
|
||||
Color get pickerIconColor {
|
||||
return Theme.of(this).isLightMode ? const Color(0xFF171717) : Colors.white;
|
||||
}
|
||||
|
||||
Color get pickerSearchBarBorderColor {
|
||||
return Theme.of(this).isLightMode
|
||||
? const Color(0x1E171717)
|
||||
: Colors.white.withOpacity(0.12);
|
||||
}
|
||||
|
||||
Color get pickerButtonBoarderColor {
|
||||
return Theme.of(this).isLightMode
|
||||
? const Color(0x1E171717)
|
||||
: Colors.white.withOpacity(0.12);
|
||||
}
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/emoji_skin_tone.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
|
||||
|
||||
import 'colors.dart';
|
||||
|
||||
typedef EmojiKeywordChangedCallback = void Function(String keyword);
|
||||
typedef EmojiSkinToneChanged = void Function(EmojiSkinTone skinTone);
|
||||
|
||||
@ -82,7 +84,7 @@ class _RandomEmojiButton extends StatelessWidget {
|
||||
height: 36,
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedRectangleBorder(
|
||||
side: const BorderSide(color: Color(0x1E171717)),
|
||||
side: BorderSide(color: context.pickerButtonBoarderColor),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
@ -141,7 +143,7 @@ class _SearchTextFieldState extends State<_SearchTextField> {
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
enableBorderColor: const Color(0x1E171717),
|
||||
enableBorderColor: context.pickerSearchBarBorderColor,
|
||||
controller: controller,
|
||||
onChanged: widget.onKeywordChanged,
|
||||
prefixIcon: const Padding(
|
||||
|
@ -5,6 +5,8 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
|
||||
|
||||
import 'colors.dart';
|
||||
|
||||
// use a temporary global value to store last selected skin tone
|
||||
EmojiSkinTone? lastSelectedEmojiSkinTone;
|
||||
|
||||
@ -68,7 +70,7 @@ class _FlowyEmojiSkinToneSelectorState
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: const Color(0x1E171717)),
|
||||
border: Border.all(color: context.pickerButtonBoarderColor),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: FlowyButton(
|
@ -1,7 +1,10 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/icon.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/icon_search_bar.dart';
|
||||
import 'package:appflowy/util/debounce.dart';
|
||||
@ -9,10 +12,13 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_ic
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart' hide Icon;
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'colors.dart';
|
||||
import 'icon_color_picker.dart';
|
||||
|
||||
// cache the icon groups to avoid loading them multiple times
|
||||
@ -197,10 +203,10 @@ class _IconPickerState extends State<IconPicker> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FlowyText(
|
||||
iconGroup.displayName,
|
||||
iconGroup.displayName.capitalize(),
|
||||
fontSize: 12,
|
||||
figmaLineHeight: 18.0,
|
||||
color: const Color(0x80171717),
|
||||
color: context.pickerTextColor,
|
||||
),
|
||||
const VSpace(4.0),
|
||||
Wrap(
|
||||
@ -218,6 +224,10 @@ class _IconPickerState extends State<IconPicker> {
|
||||
).toList(),
|
||||
),
|
||||
const VSpace(12.0),
|
||||
if (index == widget.iconGroups.length - 1) ...[
|
||||
const _StreamlinePermit(),
|
||||
const VSpace(12.0),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
@ -252,7 +262,7 @@ class _Icon extends StatelessWidget {
|
||||
child: FlowySvg.string(
|
||||
icon.content,
|
||||
size: const Size.square(20),
|
||||
color: const Color(0xFF171717),
|
||||
color: context.pickerIconColor,
|
||||
opacity: 0.7,
|
||||
),
|
||||
),
|
||||
@ -269,3 +279,39 @@ class _Icon extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StreamlinePermit extends StatelessWidget {
|
||||
const _StreamlinePermit();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Open source icons from Streamline
|
||||
final textStyle = TextStyle(
|
||||
fontSize: 12.0,
|
||||
height: 18.0 / 12.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: context.pickerTextColor,
|
||||
);
|
||||
return RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${LocaleKeys.emoji_openSourceIconsFrom.tr()} ',
|
||||
style: textStyle,
|
||||
),
|
||||
TextSpan(
|
||||
text: 'Streamline',
|
||||
style: textStyle.copyWith(
|
||||
decoration: TextDecoration.underline,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
afLaunchUrlString('https://www.streamlinehq.com/');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
|
||||
|
||||
import 'colors.dart';
|
||||
|
||||
typedef IconKeywordChangedCallback = void Function(String keyword);
|
||||
typedef EmojiSkinToneChanged = void Function(EmojiSkinTone skinTone);
|
||||
|
||||
@ -70,7 +72,7 @@ class _RandomIconButton extends StatelessWidget {
|
||||
height: 36,
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedRectangleBorder(
|
||||
side: const BorderSide(color: Color(0x1E171717)),
|
||||
side: BorderSide(color: context.pickerButtonBoarderColor),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
@ -123,7 +125,7 @@ class _SearchTextFieldState extends State<_SearchTextField> {
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
enableBorderColor: const Color(0x1E171717),
|
||||
enableBorderColor: context.pickerSearchBarBorderColor,
|
||||
controller: controller,
|
||||
onChanged: widget.onKeywordChanged,
|
||||
prefixIcon: const Padding(
|
||||
|
@ -24,6 +24,8 @@ part 'reminder_bloc.freezed.dart';
|
||||
|
||||
class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
|
||||
ReminderBloc() : super(ReminderState()) {
|
||||
Log.info('ReminderBloc created');
|
||||
|
||||
_actionBloc = getIt<ActionNavigationBloc>();
|
||||
_reminderService = const ReminderService();
|
||||
timer = _periodicCheck();
|
||||
@ -40,36 +42,58 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
started: () async {
|
||||
final remindersOrFailure = await _reminderService.fetchReminders();
|
||||
Log.info('Start fetching reminders');
|
||||
|
||||
remindersOrFailure.fold(
|
||||
(reminders) => emit(state.copyWith(reminders: reminders)),
|
||||
(error) => Log.error(error),
|
||||
final result = await _reminderService.fetchReminders();
|
||||
|
||||
result.fold(
|
||||
(reminders) {
|
||||
Log.info('Fetched reminders on startup: ${reminders.length}');
|
||||
emit(state.copyWith(reminders: reminders));
|
||||
},
|
||||
(error) => Log.error('Failed to fetch reminders: $error'),
|
||||
);
|
||||
},
|
||||
remove: (reminderId) async {
|
||||
final unitOrFailure =
|
||||
await _reminderService.removeReminder(reminderId: reminderId);
|
||||
final result = await _reminderService.removeReminder(
|
||||
reminderId: reminderId,
|
||||
);
|
||||
|
||||
unitOrFailure.fold(
|
||||
result.fold(
|
||||
(_) {
|
||||
Log.info('Removed reminder: $reminderId');
|
||||
final reminders = [...state.reminders];
|
||||
reminders.removeWhere((e) => e.id == reminderId);
|
||||
emit(state.copyWith(reminders: reminders));
|
||||
},
|
||||
(error) => Log.error(error),
|
||||
(error) => Log.error(
|
||||
'Failed to remove reminder($reminderId): $error',
|
||||
),
|
||||
);
|
||||
},
|
||||
add: (reminder) async {
|
||||
final unitOrFailure =
|
||||
await _reminderService.addReminder(reminder: reminder);
|
||||
// check the timestamp in the reminder
|
||||
if (reminder.createdAt == null) {
|
||||
reminder.freeze();
|
||||
reminder = reminder.rebuild((update) {
|
||||
update.meta[ReminderMetaKeys.createdAt] =
|
||||
DateTime.now().millisecondsSinceEpoch.toString();
|
||||
});
|
||||
}
|
||||
|
||||
return unitOrFailure.fold(
|
||||
final result = await _reminderService.addReminder(
|
||||
reminder: reminder,
|
||||
);
|
||||
|
||||
return result.fold(
|
||||
(_) {
|
||||
Log.info('Added reminder: ${reminder.id}');
|
||||
Log.info('Before adding reminder: ${state.reminders.length}');
|
||||
final reminders = [...state.reminders, reminder];
|
||||
Log.info('After adding reminder: ${reminders.length}');
|
||||
emit(state.copyWith(reminders: reminders));
|
||||
},
|
||||
(error) => Log.error(error),
|
||||
(error) => Log.error('Failed to add reminder: $error'),
|
||||
);
|
||||
},
|
||||
addById: (reminderId, objectId, scheduledAt, meta) async => add(
|
||||
@ -86,8 +110,9 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
|
||||
),
|
||||
),
|
||||
update: (updateObject) async {
|
||||
final reminder = state.reminders
|
||||
.firstWhereOrNull((r) => r.id == updateObject.id);
|
||||
final reminder = state.reminders.firstWhereOrNull(
|
||||
(r) => r.id == updateObject.id,
|
||||
);
|
||||
|
||||
if (reminder == null) {
|
||||
return;
|
||||
@ -98,15 +123,20 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
|
||||
reminder: newReminder,
|
||||
);
|
||||
|
||||
Log.info('Updating reminder: ${reminder.id}');
|
||||
|
||||
failureOrUnit.fold(
|
||||
(_) {
|
||||
Log.info('Updated reminder: ${reminder.id}');
|
||||
final index =
|
||||
state.reminders.indexWhere((r) => r.id == reminder.id);
|
||||
final reminders = [...state.reminders];
|
||||
reminders.replaceRange(index, index + 1, [newReminder]);
|
||||
emit(state.copyWith(reminders: reminders));
|
||||
},
|
||||
(error) => Log.error(error),
|
||||
(error) => Log.error(
|
||||
'Failed to update reminder(${reminder.id}): $error',
|
||||
),
|
||||
);
|
||||
},
|
||||
pressReminder: (reminderId, path, view) {
|
||||
@ -157,6 +187,9 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
|
||||
},
|
||||
markAsRead: (reminderIds) async {
|
||||
final reminders = await _onMarkAsRead(reminderIds: reminderIds);
|
||||
|
||||
Log.info('Marked reminders as read: $reminderIds');
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
reminders: reminders,
|
||||
@ -168,6 +201,9 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
|
||||
isArchived: true,
|
||||
reminderIds: reminderIds,
|
||||
);
|
||||
|
||||
Log.info('Archived reminders: $reminderIds');
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
reminders: reminders,
|
||||
@ -176,6 +212,9 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
|
||||
},
|
||||
markAllRead: () async {
|
||||
final reminders = await _onMarkAsRead();
|
||||
|
||||
Log.info('Marked all reminders as read');
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
reminders: reminders,
|
||||
@ -184,6 +223,9 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
|
||||
},
|
||||
archiveAll: () async {
|
||||
final reminders = await _onArchived(isArchived: true);
|
||||
|
||||
Log.info('Archived all reminders');
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
reminders: reminders,
|
||||
@ -199,11 +241,14 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
|
||||
);
|
||||
},
|
||||
refresh: () async {
|
||||
final remindersOrFailure = await _reminderService.fetchReminders();
|
||||
final result = await _reminderService.fetchReminders();
|
||||
|
||||
remindersOrFailure.fold(
|
||||
(reminders) => emit(state.copyWith(reminders: reminders)),
|
||||
(error) => emit(state),
|
||||
result.fold(
|
||||
(reminders) {
|
||||
Log.info('Fetched reminders on refresh: ${reminders.length}');
|
||||
emit(state.copyWith(reminders: reminders));
|
||||
},
|
||||
(error) => Log.error('Failed to fetch reminders: $error'),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
@ -10,12 +11,12 @@ part 'local_ai_on_boarding_bloc.freezed.dart';
|
||||
|
||||
class LocalAIOnBoardingBloc
|
||||
extends Bloc<LocalAIOnBoardingEvent, LocalAIOnBoardingState> {
|
||||
LocalAIOnBoardingBloc(this.workspaceId)
|
||||
LocalAIOnBoardingBloc(this.userProfile)
|
||||
: super(const LocalAIOnBoardingState()) {
|
||||
_dispatch();
|
||||
}
|
||||
|
||||
final String workspaceId;
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
void _dispatch() {
|
||||
on<LocalAIOnBoardingEvent>((event, emit) {
|
||||
@ -44,7 +45,7 @@ class LocalAIOnBoardingBloc
|
||||
}
|
||||
|
||||
void _loadSubscriptionPlans() {
|
||||
final payload = UserWorkspaceIdPB()..workspaceId = workspaceId;
|
||||
final payload = UserWorkspaceIdPB()..workspaceId = userProfile.workspaceId;
|
||||
UserEventGetWorkspaceSubscriptionInfo(payload).send().then((result) {
|
||||
if (!isClosed) {
|
||||
add(LocalAIOnBoardingEvent.didGetSubscriptionPlans(result));
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/user/application/user_listener.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
@ -10,14 +11,31 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'settings_ai_bloc.freezed.dart';
|
||||
|
||||
class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
SettingsAIBloc(this.userProfile)
|
||||
SettingsAIBloc(this.userProfile, WorkspaceMemberPB? member)
|
||||
: _userListener = UserListener(userProfile: userProfile),
|
||||
super(SettingsAIState(userProfile: userProfile)) {
|
||||
_userService = UserBackendService(userId: userProfile.id),
|
||||
super(SettingsAIState(userProfile: userProfile, member: member)) {
|
||||
_dispatch();
|
||||
|
||||
if (member == null) {
|
||||
_userService.getWorkspaceMember().then((result) {
|
||||
result.fold(
|
||||
(member) {
|
||||
if (!isClosed) {
|
||||
add(SettingsAIEvent.refreshMember(member));
|
||||
}
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
final UserListener _userListener;
|
||||
final UserProfilePB userProfile;
|
||||
final UserBackendService _userService;
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
@ -62,6 +80,9 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
),
|
||||
);
|
||||
},
|
||||
refreshMember: (member) {
|
||||
emit(state.copyWith(member: member));
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -112,6 +133,7 @@ class SettingsAIEvent with _$SettingsAIEvent {
|
||||
) = _DidLoadWorkspaceSetting;
|
||||
|
||||
const factory SettingsAIEvent.toggleAISearch() = _toggleAISearch;
|
||||
const factory SettingsAIEvent.refreshMember(WorkspaceMemberPB member) = _RefreshMember;
|
||||
|
||||
const factory SettingsAIEvent.selectModel(AIModelPB model) = _SelectAIModel;
|
||||
|
||||
@ -125,6 +147,7 @@ class SettingsAIState with _$SettingsAIState {
|
||||
const factory SettingsAIState({
|
||||
required UserProfilePB userProfile,
|
||||
UseAISettingPB? aiSettings,
|
||||
WorkspaceMemberPB? member,
|
||||
@Default(true) bool enableSearchIndexing,
|
||||
}) = _SettingsAIState;
|
||||
}
|
||||
|
@ -71,8 +71,7 @@ class DesktopHomeScreen extends StatelessWidget {
|
||||
key: ValueKey(userProfile.id),
|
||||
providers: [
|
||||
BlocProvider.value(
|
||||
value: getIt<ReminderBloc>()
|
||||
..add(const ReminderEvent.started()),
|
||||
value: getIt<ReminderBloc>(),
|
||||
),
|
||||
BlocProvider<TabsBloc>.value(value: getIt<TabsBloc>()),
|
||||
BlocProvider<HomeBloc>(
|
||||
|
@ -2,6 +2,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/_extension.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@ -17,7 +18,7 @@ class CreateSpacePopup extends StatefulWidget {
|
||||
|
||||
class _CreateSpacePopupState extends State<CreateSpacePopup> {
|
||||
String spaceName = LocaleKeys.space_defaultSpaceName.tr();
|
||||
String? spaceIcon = builtInSpaceIcons.first;
|
||||
String? spaceIcon = kDefaultSpaceIconId;
|
||||
String? spaceIconColor = builtInSpaceColors.first;
|
||||
SpacePermission spacePermission = SpacePermission.publicToAll;
|
||||
|
||||
@ -47,6 +48,7 @@ class _CreateSpacePopupState extends State<CreateSpacePopup> {
|
||||
SizedBox.square(
|
||||
dimension: 56,
|
||||
child: SpaceIconPopup(
|
||||
|
||||
onIconChanged: (icon, iconColor) {
|
||||
spaceIcon = icon;
|
||||
spaceIconColor = iconColor;
|
||||
|
@ -99,6 +99,7 @@ class _SpaceNameTextField extends StatelessWidget {
|
||||
SizedBox.square(
|
||||
dimension: 40,
|
||||
child: SpaceIconPopup(
|
||||
space: space,
|
||||
cornerRadius: 12,
|
||||
icon: space?.spaceIcon,
|
||||
iconColor: space?.spaceIconColor,
|
||||
|
@ -496,17 +496,13 @@ class CurrentSpace extends StatelessWidget {
|
||||
final child = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (space.spaceIcon != null) ...[
|
||||
SpaceIcon(
|
||||
dimension: 22,
|
||||
space: space,
|
||||
svgSize: 12,
|
||||
cornerRadius: 8.0,
|
||||
),
|
||||
const HSpace(10),
|
||||
] else ...[
|
||||
const HSpace(2),
|
||||
],
|
||||
SpaceIcon(
|
||||
dimension: 22,
|
||||
space: space,
|
||||
svgSize: 12,
|
||||
cornerRadius: 8.0,
|
||||
),
|
||||
const HSpace(10),
|
||||
Flexible(
|
||||
child: FlowyText.medium(
|
||||
space.name,
|
||||
|
@ -87,6 +87,7 @@ class _SidebarSpaceMenuItem extends StatelessWidget {
|
||||
leftIcon: SpaceIcon(
|
||||
dimension: 20,
|
||||
space: space,
|
||||
svgSize: 12.0,
|
||||
cornerRadius: 6.0,
|
||||
),
|
||||
leftIconSize: const Size.square(20),
|
||||
|
@ -1,35 +1,61 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SpaceIcon extends StatelessWidget {
|
||||
const SpaceIcon({
|
||||
super.key,
|
||||
required this.dimension,
|
||||
this.textDimension,
|
||||
this.cornerRadius = 0,
|
||||
required this.space,
|
||||
this.svgSize,
|
||||
});
|
||||
|
||||
final double dimension;
|
||||
final double? textDimension;
|
||||
final double cornerRadius;
|
||||
final ViewPB space;
|
||||
final double? svgSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final spaceIconColor = space.spaceIconColor;
|
||||
final color = spaceIconColor != null
|
||||
? Color(int.parse(spaceIconColor))
|
||||
: Colors.transparent;
|
||||
final svg = space.buildSpaceIconSvg(
|
||||
context,
|
||||
size: svgSize != null ? Size.square(svgSize!) : null,
|
||||
);
|
||||
if (svg == null) {
|
||||
return const SizedBox.shrink();
|
||||
// if space icon is null, use the first character of space name as icon
|
||||
|
||||
final Color color;
|
||||
final Widget icon;
|
||||
|
||||
if (space.spaceIcon == null) {
|
||||
final name = space.name.isNotEmpty ? space.name.capitalize()[0] : '';
|
||||
icon = FlowyText.medium(
|
||||
name,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
fontSize: svgSize,
|
||||
figmaLineHeight: textDimension ?? dimension,
|
||||
);
|
||||
color = Color(int.parse(builtInSpaceColors.first));
|
||||
} else {
|
||||
final spaceIconColor = space.spaceIconColor;
|
||||
color = spaceIconColor != null
|
||||
? Color(int.parse(spaceIconColor))
|
||||
: Colors.transparent;
|
||||
final svg = space.buildSpaceIconSvg(
|
||||
context,
|
||||
size: svgSize != null ? Size.square(svgSize!) : null,
|
||||
);
|
||||
if (svg == null) {
|
||||
icon = const SizedBox.shrink();
|
||||
} else {
|
||||
icon =
|
||||
svgSize == null || space.spaceIcon?.contains('space_icon') == true
|
||||
? svg
|
||||
: SizedBox.square(dimension: svgSize!, child: svg);
|
||||
}
|
||||
}
|
||||
|
||||
return ClipRRect(
|
||||
@ -39,16 +65,15 @@ class SpaceIcon extends StatelessWidget {
|
||||
height: dimension,
|
||||
color: color,
|
||||
child: Center(
|
||||
child:
|
||||
svgSize == null || space.spaceIcon?.contains('space_icon') == true
|
||||
? svg
|
||||
: SizedBox.square(dimension: svgSize!, child: svg),
|
||||
child: icon,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const kDefaultSpaceIconId = 'interface_essential/home-3';
|
||||
|
||||
class DefaultSpaceIcon extends StatelessWidget {
|
||||
const DefaultSpaceIcon({
|
||||
super.key,
|
||||
@ -63,7 +88,25 @@ class DefaultSpaceIcon extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final svg = builtInSpaceIcons.first;
|
||||
final svgContent = kIconGroups?.findSvgContent(
|
||||
kDefaultSpaceIconId,
|
||||
);
|
||||
|
||||
final Widget svg;
|
||||
if (svgContent != null) {
|
||||
svg = FlowySvg.string(
|
||||
svgContent,
|
||||
size: Size.square(iconDimension),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
);
|
||||
} else {
|
||||
svg = FlowySvg(
|
||||
FlowySvgData('assets/flowy_icons/16x/${builtInSpaceIcons.first}.svg'),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
size: Size.square(iconDimension),
|
||||
);
|
||||
}
|
||||
|
||||
final color = Color(int.parse(builtInSpaceColors.first));
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(cornerRadius),
|
||||
@ -71,10 +114,8 @@ class DefaultSpaceIcon extends StatelessWidget {
|
||||
width: dimension,
|
||||
height: dimension,
|
||||
color: color,
|
||||
child: FlowySvg(
|
||||
FlowySvgData('assets/flowy_icons/16x/$svg.svg'),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
size: Size.square(iconDimension),
|
||||
child: Center(
|
||||
child: svg,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -6,6 +6,7 @@ import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/tab.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@ -40,11 +41,13 @@ class SpaceIconPopup extends StatefulWidget {
|
||||
this.icon,
|
||||
this.iconColor,
|
||||
this.cornerRadius = 16,
|
||||
this.space,
|
||||
required this.onIconChanged,
|
||||
});
|
||||
|
||||
final String? icon;
|
||||
final String? iconColor;
|
||||
final ViewPB? space;
|
||||
final void Function(String? icon, String? color) onIconChanged;
|
||||
final double cornerRadius;
|
||||
|
||||
@ -114,11 +117,20 @@ class _SpaceIconPopupState extends State<SpaceIconPopup> {
|
||||
builder: (_, value, __) {
|
||||
Widget child;
|
||||
if (value == null) {
|
||||
child = const DefaultSpaceIcon(
|
||||
cornerRadius: 16.0,
|
||||
dimension: 32,
|
||||
iconDimension: 32,
|
||||
);
|
||||
if (widget.space == null) {
|
||||
child = DefaultSpaceIcon(
|
||||
cornerRadius: widget.cornerRadius,
|
||||
dimension: 32,
|
||||
iconDimension: 32,
|
||||
);
|
||||
} else {
|
||||
child = SpaceIcon(
|
||||
dimension: 32,
|
||||
space: widget.space!,
|
||||
svgSize: 24,
|
||||
cornerRadius: widget.cornerRadius,
|
||||
);
|
||||
}
|
||||
} else if (value.contains('space_icon')) {
|
||||
child = ClipRRect(
|
||||
borderRadius: BorderRadius.circular(widget.cornerRadius),
|
||||
|
@ -108,7 +108,7 @@ class SpaceMoreActionTypeWrapper extends CustomActionCell {
|
||||
) {
|
||||
final child = _buildActionButton(context, null);
|
||||
return AppFlowyPopover(
|
||||
constraints: BoxConstraints.loose(const Size(380, 432)),
|
||||
constraints: BoxConstraints.loose(const Size(360, 432)),
|
||||
margin: const EdgeInsets.all(0),
|
||||
clickHandler: PopoverClickHandler.gestureDetector,
|
||||
offset: const Offset(0, -40),
|
||||
|
@ -85,14 +85,16 @@ class _WorkspaceMoreActionWrapper extends CustomActionCell {
|
||||
BuildContext context,
|
||||
PopoverController controller,
|
||||
) {
|
||||
return FlowyButton(
|
||||
leftIcon: buildLeftIcon(context),
|
||||
return FlowyIconTextButton(
|
||||
leftIconBuilder: (onHover) => buildLeftIcon(context, onHover),
|
||||
iconPadding: 10.0,
|
||||
text: FlowyText.regular(
|
||||
textBuilder: (onHover) => FlowyText.regular(
|
||||
name,
|
||||
fontSize: 14.0,
|
||||
figmaLineHeight: 18.0,
|
||||
color: [WorkspaceMoreAction.delete, WorkspaceMoreAction.leave]
|
||||
.contains(inner)
|
||||
.contains(inner) &&
|
||||
onHover
|
||||
? Theme.of(context).colorScheme.error
|
||||
: null,
|
||||
),
|
||||
@ -161,12 +163,12 @@ class _WorkspaceMoreActionWrapper extends CustomActionCell {
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildLeftIcon(BuildContext context) {
|
||||
Widget buildLeftIcon(BuildContext context, bool onHover) {
|
||||
switch (inner) {
|
||||
case WorkspaceMoreAction.delete:
|
||||
return FlowySvg(
|
||||
FlowySvgs.delete_s,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
color: onHover ? Theme.of(context).colorScheme.error : null,
|
||||
);
|
||||
case WorkspaceMoreAction.rename:
|
||||
return const FlowySvg(FlowySvgs.view_item_rename_s);
|
||||
|
@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/notification_filter/notification_filter_bloc.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
@ -11,6 +9,7 @@ import 'package:appflowy/workspace/presentation/notifications/widgets/notificati
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/reminder.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class NotificationDialog extends StatefulWidget {
|
||||
@ -64,12 +63,10 @@ class _NotificationDialogState extends State<NotificationDialog>
|
||||
builder: (context, filterState) =>
|
||||
BlocBuilder<ReminderBloc, ReminderState>(
|
||||
builder: (context, state) {
|
||||
final List<ReminderPB> pastReminders = state.pastReminders
|
||||
.where((r) => filterState.showUnreadsOnly ? !r.isRead : true)
|
||||
.sortByScheduledAt();
|
||||
|
||||
final List<ReminderPB> upcomingReminders =
|
||||
final reminders = state.reminders.sortByScheduledAt();
|
||||
final upcomingReminders =
|
||||
state.upcomingReminders.sortByScheduledAt();
|
||||
final hasUnreads = reminders.any((r) => !r.isRead);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -82,14 +79,14 @@ class _NotificationDialogState extends State<NotificationDialog>
|
||||
controller: _controller,
|
||||
children: [
|
||||
NotificationsView(
|
||||
shownReminders: pastReminders,
|
||||
shownReminders: reminders,
|
||||
reminderBloc: _reminderBloc,
|
||||
views: widget.views,
|
||||
onDelete: _onDelete,
|
||||
onAction: _onAction,
|
||||
onReadChanged: _onReadChanged,
|
||||
actionBar: InboxActionBar(
|
||||
hasUnreads: state.hasUnreads,
|
||||
hasUnreads: hasUnreads,
|
||||
showUnreadsOnly: filterState.showUnreadsOnly,
|
||||
),
|
||||
),
|
||||
|
@ -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/startup/startup.dart';
|
||||
@ -11,6 +9,7 @@ import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
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:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class NotificationButton extends StatefulWidget {
|
||||
@ -23,6 +22,12 @@ class NotificationButton extends StatefulWidget {
|
||||
class _NotificationButtonState extends State<NotificationButton> {
|
||||
final mutex = PopoverMutex();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getIt<ReminderBloc>().add(const ReminderEvent.started());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
mutex.dispose();
|
||||
@ -56,8 +61,10 @@ class _NotificationButtonState extends State<NotificationButton> {
|
||||
child: FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
margin: EdgeInsets.zero,
|
||||
text:
|
||||
_buildNotificationIcon(context, state.hasUnreads),
|
||||
text: _buildNotificationIcon(
|
||||
context,
|
||||
state.hasUnreads,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||
import 'package:appflowy/mobile/presentation/notifications/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -18,7 +18,7 @@ import 'package:provider/provider.dart';
|
||||
class NotificationItem extends StatefulWidget {
|
||||
const NotificationItem({
|
||||
super.key,
|
||||
required this.reminderId,
|
||||
required this.reminder,
|
||||
required this.title,
|
||||
required this.scheduled,
|
||||
required this.body,
|
||||
@ -32,7 +32,7 @@ class NotificationItem extends StatefulWidget {
|
||||
this.view,
|
||||
});
|
||||
|
||||
final String reminderId;
|
||||
final ReminderPB reminder;
|
||||
final String title;
|
||||
final Int64 scheduled;
|
||||
final String body;
|
||||
@ -169,6 +169,7 @@ class _NotificationItemState extends State<NotificationItem> {
|
||||
),
|
||||
child: _NotificationContent(
|
||||
block: widget.block,
|
||||
reminder: widget.reminder,
|
||||
body: widget.body,
|
||||
),
|
||||
),
|
||||
@ -214,10 +215,12 @@ class _NotificationItemState extends State<NotificationItem> {
|
||||
class _NotificationContent extends StatelessWidget {
|
||||
const _NotificationContent({
|
||||
required this.body,
|
||||
required this.reminder,
|
||||
required this.block,
|
||||
});
|
||||
|
||||
final String body;
|
||||
final ReminderPB reminder;
|
||||
final Future<Node?>? block;
|
||||
|
||||
@override
|
||||
@ -229,29 +232,10 @@ class _NotificationContent extends StatelessWidget {
|
||||
return FlowyText.regular(body, maxLines: 4);
|
||||
}
|
||||
|
||||
final editorState = EditorState(
|
||||
document: Document(root: snapshot.data!),
|
||||
);
|
||||
|
||||
final styleCustomizer = EditorStyleCustomizer(
|
||||
context: context,
|
||||
padding: EdgeInsets.zero,
|
||||
);
|
||||
|
||||
return Transform.scale(
|
||||
scale: .9,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: AppFlowyEditor(
|
||||
editorState: editorState,
|
||||
editorStyle: styleCustomizer.style(),
|
||||
editable: false,
|
||||
shrinkWrap: true,
|
||||
blockComponentBuilders: getEditorBuilderMap(
|
||||
context: context,
|
||||
editorState: editorState,
|
||||
styleCustomizer: styleCustomizer,
|
||||
editable: false,
|
||||
),
|
||||
return IntrinsicHeight(
|
||||
child: NotificationDocumentContent(
|
||||
nodes: [snapshot.data!],
|
||||
reminder: reminder,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -76,7 +76,7 @@ class NotificationsView extends StatelessWidget {
|
||||
|
||||
final view = views.findView(reminder.objectId);
|
||||
return NotificationItem(
|
||||
reminderId: reminder.id,
|
||||
reminder: reminder,
|
||||
key: ValueKey(reminder.id),
|
||||
title: reminder.title,
|
||||
scheduled: reminder.scheduledAt,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/shared/af_role_pb_extension.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/local_ai_on_boarding_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
||||
@ -37,15 +38,20 @@ class AIFeatureOnlySupportedWhenUsingAppFlowyCloud extends StatelessWidget {
|
||||
}
|
||||
|
||||
class SettingsAIView extends StatelessWidget {
|
||||
const SettingsAIView({super.key, required this.userProfile});
|
||||
const SettingsAIView({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
required this.member,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
final WorkspaceMemberPB? member;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<SettingsAIBloc>(
|
||||
create: (_) =>
|
||||
SettingsAIBloc(userProfile)..add(const SettingsAIEvent.started()),
|
||||
create: (_) => SettingsAIBloc(userProfile, member)
|
||||
..add(const SettingsAIEvent.started()),
|
||||
child: BlocBuilder<SettingsAIBloc, SettingsAIState>(
|
||||
builder: (context, state) {
|
||||
final children = <Widget>[
|
||||
@ -53,11 +59,15 @@ class SettingsAIView extends StatelessWidget {
|
||||
];
|
||||
|
||||
children.add(const _AISearchToggle(value: false));
|
||||
children.add(
|
||||
_LocalAIOnBoarding(
|
||||
workspaceId: userProfile.workspaceId,
|
||||
),
|
||||
);
|
||||
|
||||
if (state.member != null) {
|
||||
children.add(
|
||||
_LocalAIOnBoarding(
|
||||
userProfile: userProfile,
|
||||
member: state.member!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SettingsBody(
|
||||
title: LocaleKeys.settings_aiPage_title.tr(),
|
||||
@ -116,8 +126,12 @@ class _AISearchToggle extends StatelessWidget {
|
||||
|
||||
// ignore: unused_element
|
||||
class _LocalAIOnBoarding extends StatelessWidget {
|
||||
const _LocalAIOnBoarding({required this.workspaceId});
|
||||
final String workspaceId;
|
||||
const _LocalAIOnBoarding({
|
||||
required this.userProfile,
|
||||
required this.member,
|
||||
});
|
||||
final UserProfilePB userProfile;
|
||||
final WorkspaceMemberPB member;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -125,7 +139,7 @@ class _LocalAIOnBoarding extends StatelessWidget {
|
||||
return BillingGateGuard(
|
||||
builder: (context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LocalAIOnBoardingBloc(workspaceId)
|
||||
create: (context) => LocalAIOnBoardingBloc(userProfile)
|
||||
..add(const LocalAIOnBoardingEvent.started()),
|
||||
child: BlocBuilder<LocalAIOnBoardingBloc, LocalAIOnBoardingState>(
|
||||
builder: (context, state) {
|
||||
@ -133,16 +147,20 @@ class _LocalAIOnBoarding extends StatelessWidget {
|
||||
if (kDebugMode || state.isPurchaseAILocal) {
|
||||
return const LocalAISetting();
|
||||
} else {
|
||||
// Show the upgrade to AI Local plan button if the user has not purchased the AI Local plan
|
||||
return _UpgradeToAILocalPlan(
|
||||
onTap: () {
|
||||
context.read<SettingsDialogBloc>().add(
|
||||
const SettingsDialogEvent.setSelectedPage(
|
||||
SettingsPage.plan,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (member.role.isOwner) {
|
||||
// Show the upgrade to AI Local plan button if the user has not purchased the AI Local plan
|
||||
return _UpgradeToAILocalPlan(
|
||||
onTap: () {
|
||||
context.read<SettingsDialogBloc>().add(
|
||||
const SettingsDialogEvent.setSelectedPage(
|
||||
SettingsPage.plan,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return const _AskOwnerUpgradeToLocalAI();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -155,6 +173,18 @@ class _LocalAIOnBoarding extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _AskOwnerUpgradeToLocalAI extends StatelessWidget {
|
||||
const _AskOwnerUpgradeToLocalAI();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyText(
|
||||
LocaleKeys.sideBar_askOwnerToUpgradeToLocalAI.tr(),
|
||||
color: AFThemeExtension.of(context).strongText,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UpgradeToAILocalPlan extends StatefulWidget {
|
||||
const _UpgradeToAILocalPlan({required this.onTap});
|
||||
|
||||
|
@ -120,7 +120,7 @@ class SettingsDialog extends StatelessWidget {
|
||||
return const SettingsShortcutsView();
|
||||
case SettingsPage.ai:
|
||||
if (user.authenticator == AuthenticatorPB.AppFlowyCloud) {
|
||||
return SettingsAIView(userProfile: user);
|
||||
return SettingsAIView(userProfile: user, member: member);
|
||||
} else {
|
||||
return const AIFeatureOnlySupportedWhenUsingAppFlowyCloud();
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ class FlowyTooltip extends StatelessWidget {
|
||||
this.preferBelow,
|
||||
this.showDuration,
|
||||
this.margin,
|
||||
this.verticalOffset,
|
||||
this.child,
|
||||
});
|
||||
|
||||
@ -19,6 +20,7 @@ class FlowyTooltip extends StatelessWidget {
|
||||
final Duration? showDuration;
|
||||
final EdgeInsetsGeometry? margin;
|
||||
final Widget? child;
|
||||
final double? verticalOffset;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -28,16 +30,14 @@ class FlowyTooltip extends StatelessWidget {
|
||||
|
||||
return Tooltip(
|
||||
margin: margin,
|
||||
verticalOffset: 16.0,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12.0,
|
||||
right: 12.0,
|
||||
top: 5.0,
|
||||
bottom: 8.0,
|
||||
verticalOffset: verticalOffset ?? 16.0,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: context.tooltipBackgroundColor(),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
waitDuration: _tooltipWaitDuration,
|
||||
message: message,
|
||||
@ -50,8 +50,8 @@ class FlowyTooltip extends StatelessWidget {
|
||||
}
|
||||
|
||||
extension FlowyToolTipExtension on BuildContext {
|
||||
double tooltipFontSize() => 13.0;
|
||||
double tooltipHeight() => 18.0 / tooltipFontSize();
|
||||
double tooltipFontSize() => 14.0;
|
||||
double tooltipHeight() => 20.0 / tooltipFontSize();
|
||||
Color tooltipFontColor() => Theme.of(this).brightness == Brightness.light
|
||||
? Colors.white
|
||||
: Colors.black;
|
||||
@ -62,6 +62,7 @@ extension FlowyToolTipExtension on BuildContext {
|
||||
fontSize: tooltipFontSize(),
|
||||
fontWeight: FontWeight.w400,
|
||||
height: tooltipHeight(),
|
||||
leadingDistribution: TextLeadingDistribution.even,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -53,8 +53,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: c064543
|
||||
resolved-ref: c064543e0e40f862f1db2db36725d48465a8aea5
|
||||
ref: "4536488faf458ab45e304c1715850d4d1ae517ee"
|
||||
resolved-ref: "4536488faf458ab45e304c1715850d4d1ae517ee"
|
||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||
source: git
|
||||
version: "3.1.0"
|
||||
|
@ -199,7 +199,7 @@ dependency_overrides:
|
||||
appflowy_editor:
|
||||
git:
|
||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||
ref: "c064543"
|
||||
ref: "4536488faf458ab45e304c1715850d4d1ae517ee"
|
||||
|
||||
appflowy_editor_plugins:
|
||||
git:
|
||||
|
26
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -172,7 +172,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=30c7acce96f1a7b8865c05e70b6e525eaa286b37#30c7acce96f1a7b8865c05e70b6e525eaa286b37"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -192,7 +192,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-ai-client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=30c7acce96f1a7b8865c05e70b6e525eaa286b37#30c7acce96f1a7b8865c05e70b6e525eaa286b37"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -423,7 +423,7 @@ dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.10.5",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"proc-macro2",
|
||||
@ -826,7 +826,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=30c7acce96f1a7b8865c05e70b6e525eaa286b37#30c7acce96f1a7b8865c05e70b6e525eaa286b37"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@ -876,7 +876,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=30c7acce96f1a7b8865c05e70b6e525eaa286b37#30c7acce96f1a7b8865c05e70b6e525eaa286b37"
|
||||
dependencies = [
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
@ -888,7 +888,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=30c7acce96f1a7b8865c05e70b6e525eaa286b37#30c7acce96f1a7b8865c05e70b6e525eaa286b37"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@ -1132,7 +1132,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=30c7acce96f1a7b8865c05e70b6e525eaa286b37#30c7acce96f1a7b8865c05e70b6e525eaa286b37"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -1157,7 +1157,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=30c7acce96f1a7b8865c05e70b6e525eaa286b37#30c7acce96f1a7b8865c05e70b6e525eaa286b37"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1532,7 +1532,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=30c7acce96f1a7b8865c05e70b6e525eaa286b37#30c7acce96f1a7b8865c05e70b6e525eaa286b37"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -3051,7 +3051,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=30c7acce96f1a7b8865c05e70b6e525eaa286b37#30c7acce96f1a7b8865c05e70b6e525eaa286b37"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@ -3068,7 +3068,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=30c7acce96f1a7b8865c05e70b6e525eaa286b37#30c7acce96f1a7b8865c05e70b6e525eaa286b37"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -3500,7 +3500,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=30c7acce96f1a7b8865c05e70b6e525eaa286b37#30c7acce96f1a7b8865c05e70b6e525eaa286b37"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -6098,7 +6098,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=30c7acce96f1a7b8865c05e70b6e525eaa286b37#30c7acce96f1a7b8865c05e70b6e525eaa286b37"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
|
@ -53,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 = "a371912c61d79fa946ec78f0cb852fdd7d391356" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "30c7acce96f1a7b8865c05e70b6e525eaa286b37" }
|
||||
|
||||
[dependencies]
|
||||
serde_json.workspace = true
|
||||
|
@ -75,6 +75,12 @@ http {
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location /af_icons/ {
|
||||
root /usr/share/nginx/html;
|
||||
expires 30d;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
error_page 404 /404.html;
|
||||
location = /404.html {
|
||||
root /usr/share/nginx/html;
|
||||
|
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189544)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.71468 0.0400391C9.1289 0.0400391 9.46468 0.375825 9.46468 0.790039V1.97754H10.4999C11.3283 1.97754 11.9999 2.64911 11.9999 3.47754V4.51367H13.2186C13.6328 4.51367 13.9686 4.84946 13.9686 5.26367C13.9686 5.67789 13.6328 6.01367 13.2186 6.01367H11.9999V7.94324H13.2186C13.6328 7.94324 13.9686 8.27902 13.9686 8.69324C13.9686 9.10745 13.6328 9.44324 13.2186 9.44324H11.9999V10.4775C11.9999 11.306 11.3283 11.9775 10.4999 11.9775H9.46468V13.2099C9.46468 13.6241 9.1289 13.9599 8.71468 13.9599C8.30047 13.9599 7.96468 13.6241 7.96468 13.2099V11.9775H6.03524V13.2099C6.03524 13.6241 5.69945 13.9599 5.28524 13.9599C4.87103 13.9599 4.53524 13.6241 4.53524 13.2099V11.9775H3.49984C2.67141 11.9775 1.99984 11.306 1.99984 10.4775V9.44324H0.783203C0.368989 9.44324 0.0332031 9.10745 0.0332031 8.69324C0.0332031 8.27902 0.368989 7.94324 0.783203 7.94324H1.99984V6.01367H0.783203C0.368989 6.01367 0.0332031 5.67789 0.0332031 5.26367C0.0332031 4.84946 0.368989 4.51367 0.783203 4.51367H1.99984V3.47754C1.99984 2.64911 2.67141 1.97754 3.49984 1.97754H4.53524V0.790039C4.53524 0.375825 4.87103 0.0400391 5.28524 0.0400391C5.69945 0.0400391 6.03524 0.375825 6.03524 0.790039V1.97754H7.96468V0.790039C7.96468 0.375825 8.30047 0.0400391 8.71468 0.0400391ZM6.24357 4.5682C6.41951 3.76446 7.56392 3.75946 7.74688 4.56163L7.75499 4.59732C7.76038 4.62113 7.76543 4.64342 7.7708 4.66628C7.96264 5.48335 8.62389 6.10768 9.45164 6.25169C10.2908 6.39768 10.2908 7.60232 9.45164 7.74831C8.61948 7.89308 7.95559 8.52332 7.76776 9.34682L7.74688 9.43837C7.56392 10.2405 6.41951 10.2355 6.24357 9.4318L6.22638 9.35325C6.04547 8.52684 5.38257 7.89201 4.54911 7.74701C3.71142 7.60128 3.71141 6.39872 4.54911 6.25299C5.37967 6.10849 6.04086 5.47757 6.22447 4.65539L6.23707 4.598L6.24357 4.5682Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189544">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
@ -0,0 +1,12 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189529)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M7.88381 0.165291C6.89196 0.0450664 5.88886 0.268505 5.04181 0.798345C4.25349 1.29145 3.64467 2.02219 3.30135 2.88247C2.87262 2.92977 2.45508 3.05187 2.06781 3.24362C1.63324 3.45878 1.24531 3.75744 0.92616 4.12253C0.281611 4.85986 -0.0436335 5.82304 0.0219743 6.80017C0.087582 7.77731 0.538668 8.68836 1.276 9.33291C1.52925 9.55429 1.80913 9.738 2.10708 9.88114C2.23853 9.09496 2.79956 8.38069 3.79016 8.20836C4.35848 8.10949 4.81104 7.67811 4.93736 7.11585L4.96023 7.01146C5.43591 4.83845 8.53 4.82492 9.02466 6.9937L9.03475 7.03814C9.04166 7.06865 9.047 7.09221 9.05244 7.11538C9.18449 7.6778 9.63973 8.10763 10.2096 8.20676C11.2609 8.38966 11.8287 9.18251 11.9131 10.0243C12.2792 9.884 12.6197 9.67707 12.9162 9.41068C13.5042 8.88231 13.878 8.15668 13.9669 7.37119C14.0558 6.58569 13.8536 5.79486 13.3985 5.14845C12.9811 4.55545 12.3775 4.12175 11.6866 3.91417C11.5613 2.99088 11.1436 2.12994 10.4923 1.45901C9.79633 0.742138 8.87567 0.285516 7.88381 0.165291ZM6.18132 7.27876C6.37146 6.41016 7.60823 6.40476 7.80595 7.27166L7.81577 7.3149C7.8225 7.34462 7.82879 7.37239 7.83553 7.4011C8.08204 8.451 8.93171 9.25323 9.99531 9.43826C10.9004 9.59573 10.9004 10.8951 9.99531 11.0525C8.92604 11.2386 8.07299 12.0484 7.83164 13.1065L7.80595 13.2191C7.60823 14.086 6.37146 14.0806 6.18132 13.212L6.16017 13.1154C5.92765 12.0532 5.07564 11.2373 4.0044 11.0509C3.10108 10.8938 3.10107 9.59701 4.0044 9.43986C5.07191 9.25414 5.92172 8.44323 6.15771 7.38648L6.17343 7.31494L6.18132 7.27876Z"
|
||||
fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189529">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189535)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.85637 0.654372C3.04651 -0.214226 4.28328 -0.219632 4.481 0.647271L4.49082 0.690505C4.49756 0.720231 4.50384 0.747995 4.51058 0.776709C4.75709 1.82661 5.60676 2.62884 6.67036 2.81387C7.57548 2.97134 7.57548 4.27068 6.67036 4.42815C5.60109 4.61417 4.74804 5.42398 4.50669 6.48214L4.481 6.59475C4.28328 7.46166 3.04651 7.45624 2.85637 6.58765L2.83522 6.49102C2.6027 5.42885 1.75069 4.61292 0.679451 4.42655C-0.223876 4.2694 -0.223883 2.97262 0.679451 2.81547C1.74696 2.62975 2.59677 1.81884 2.83276 0.762091L2.84848 0.690547L2.85637 0.654372ZM10.6352 3.61645C10.8194 3.53895 11.0172 3.49902 11.217 3.49902C11.4168 3.49902 11.6146 3.53895 11.7988 3.61645C11.9823 3.69369 12.1486 3.80669 12.288 3.94888L12.2895 3.95034L13.5557 5.22652C13.6955 5.36563 13.8066 5.53095 13.8824 5.71303C13.9586 5.89582 13.9978 6.09188 13.9978 6.2899C13.9978 6.48792 13.9586 6.68398 13.8824 6.86676C13.8065 7.0489 13.6955 7.21427 13.5556 7.35341L13.5541 7.3549L7.35665 13.5923C7.2733 13.6762 7.16293 13.7278 7.04513 13.738L4.04513 13.998C3.89803 14.0108 3.75281 13.9579 3.6484 13.8535C3.544 13.749 3.49108 13.6038 3.50382 13.4567L3.76382 10.4567C3.77403 10.3389 3.82566 10.2286 3.90954 10.1452L10.1473 3.94751C10.2864 3.80597 10.4522 3.69344 10.6352 3.61645Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189535">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189541)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.62026 0.0512695H10.9316C11.7887 0.0512695 12.4835 0.746078 12.4835 1.60317V1.68034L6.27595 4.78414L0.0683594 1.68035V1.60317C0.0683594 0.746078 0.763168 0.0512695 1.62026 0.0512695ZM0.0683594 3.07789V7.81076C0.0683594 8.66785 0.763168 9.36266 1.62026 9.36266H6.54725C6.80422 9.07078 7.16884 8.8528 7.64113 8.77063C8.15658 8.68096 8.56704 8.28972 8.68161 7.77976L8.70235 7.68508C9.13378 5.71422 11.94 5.70195 12.3887 7.66897L12.3978 7.70928C12.4041 7.73695 12.4089 7.75832 12.4139 7.77934C12.4295 7.84583 12.4501 7.91029 12.4752 7.9723C12.4807 7.9192 12.4835 7.86531 12.4835 7.81076V3.07788L6.55545 6.04193C6.3795 6.12991 6.17239 6.12991 5.99644 6.04193L0.0683594 3.07789ZM9.76442 7.91757C9.9475 7.08124 11.1383 7.07603 11.3287 7.91073L11.3376 7.95019C11.3436 7.97709 11.3494 8.00194 11.3555 8.02784C11.5748 8.96193 12.3307 9.67568 13.277 9.8403C14.1493 9.99206 14.1493 11.2443 13.277 11.3961C12.3257 11.5616 11.5667 12.282 11.352 13.2235L11.3287 13.3256C11.1383 14.1603 9.9475 14.1551 9.76442 13.3188L9.74524 13.2311C9.5384 12.2862 8.78045 11.5604 7.82749 11.3946C6.9568 11.2431 6.9568 9.99323 7.82749 9.84175C8.77713 9.67654 9.53312 8.95515 9.74306 8.01508L9.75722 7.95059L9.76442 7.91757Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189541">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189562)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.2889 0.770414C9.47222 -0.0670114 10.6646 -0.0722238 10.8552 0.763568L10.8642 0.803181C10.8703 0.830101 10.876 0.855164 10.8821 0.881094C11.1023 1.8191 11.8614 2.53584 12.8117 2.70116C13.6851 2.85311 13.6851 4.10695 12.8117 4.2589C11.8564 4.4251 11.0942 5.14861 10.8786 6.094L10.8552 6.19649C10.6646 7.03229 9.47222 7.02707 9.2889 6.18964L9.26965 6.1017C9.06194 5.15283 8.30081 4.42393 7.34384 4.25744C6.47205 4.10578 6.47205 2.85428 7.34384 2.70261C8.29748 2.53671 9.05664 1.81229 9.26746 0.868267L9.28167 0.803553L9.2889 0.770414ZM7.15656 5.33399C6.89004 5.28762 6.65768 5.19815 6.4595 5.07662H3.94145C2.41281 5.07662 1.12861 6.22603 0.959804 7.74533L0.558928 11.3532C0.41107 12.6839 1.45273 13.8478 2.79165 13.8478C3.64254 13.8478 4.42041 13.367 4.80094 12.6059L5.1546 11.8986H8.07831L8.43198 12.6059C8.81251 13.367 9.59038 13.8478 10.4413 13.8478C11.7802 13.8478 12.8218 12.6839 12.674 11.3532L12.2731 7.74533C12.2257 7.31878 12.0904 6.92139 11.8864 6.57041C11.3442 8.41179 8.6446 8.35624 8.22147 6.42331L8.20222 6.33537C8.08944 5.82016 7.67616 5.42438 7.15656 5.33399ZM4.68321 7.51305C4.68321 7.16787 4.40339 6.88805 4.05821 6.88805C3.71304 6.88805 3.43321 7.16787 3.43321 7.51305V8.02512H2.92129C2.57612 8.02512 2.29629 8.30495 2.29629 8.65012C2.29629 8.9953 2.57612 9.27512 2.92129 9.27512H3.43321V9.78704C3.43321 10.1322 3.71304 10.412 4.05821 10.412C4.40339 10.412 4.68321 10.1322 4.68321 9.78704V9.27512H5.19529C5.54047 9.27513 5.82029 8.9953 5.82029 8.65012C5.82029 8.30495 5.54047 8.02512 5.19529 8.02512H4.68321V7.51305ZM9.43357 9.25923C9.43357 9.59563 9.16087 9.86834 8.82447 9.86834C8.48807 9.86834 8.21536 9.59563 8.21536 9.25923C8.21536 8.92283 8.48807 8.65012 8.82447 8.65012C9.16087 8.65012 9.43357 8.92283 9.43357 9.25923Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189562">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
@ -0,0 +1,11 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189556)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.73377 10.5064C0.790259 10.5064 0.0253906 9.74157 0.0253906 8.79806V1.7234C0.0253906 0.779884 0.790258 0.0150146 1.73377 0.0150146H9.20934C10.1528 0.0150146 10.9177 0.779883 10.9177 1.7234V5.5825C9.85424 5.2672 8.55639 5.78485 8.26193 7.13002L8.23906 7.23441C8.12212 7.75488 7.72564 8.1632 7.21657 8.29944L6.84942 7.81212C5.65775 6.23042 3.67743 5.45387 1.72825 5.80394L1.52536 5.84038V8.75646C1.52536 8.89453 1.63729 9.00646 1.77536 9.00646H5.86263C5.50855 9.42383 5.34873 9.97147 5.38318 10.5064H1.73377ZM9.25336 3.16205C9.25336 3.91928 8.6395 4.53314 7.88226 4.53314C7.12503 4.53314 6.51117 3.91928 6.51117 3.16205C6.51117 2.40481 7.12503 1.79095 7.88226 1.79095C8.6395 1.79095 9.25336 2.40481 9.25336 3.16205Z" fill="black"/>
|
||||
<path d="M7.3918 10.6769C7.04087 10.6158 7.04087 10.112 7.3918 10.051C8.65873 9.83058 9.6673 8.86817 9.94739 7.61399L9.96401 7.53828L9.97145 7.50421C10.0474 7.15738 10.5412 7.15522 10.6202 7.50138L10.6255 7.52484C10.6337 7.56122 10.6419 7.59761 10.6505 7.63391C10.9427 8.8788 11.9502 9.83 13.2113 10.0494C13.564 10.1108 13.564 10.6171 13.2113 10.6785C11.9435 10.899 10.932 11.8592 10.6458 13.1139L10.6202 13.2265C10.5412 13.5726 10.0474 13.5705 9.97145 13.2236L9.9503 13.127C9.67434 11.8664 8.66316 10.8981 7.3918 10.6769Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189556">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189568)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.81278 0.6334C9.9959 -0.203147 11.1871 -0.208354 11.3775 0.626561L11.3865 0.666074C11.3926 0.69299 11.3982 0.717837 11.4043 0.743751C11.6238 1.67861 12.3803 2.39294 13.3274 2.5577C14.1999 2.7095 14.1999 3.96205 13.3274 4.11385C12.3753 4.27949 11.6157 5.00057 11.4008 5.94279L11.3775 6.04499C11.1871 6.87991 9.9959 6.87469 9.81278 6.03815L9.79358 5.95045C9.58657 5.00477 8.82799 4.27832 7.87424 4.1124C7.00335 3.96088 7.00334 2.71067 7.87424 2.55915C8.82467 2.39381 9.58129 1.67183 9.79139 0.730975L9.80557 0.666425L9.81278 0.6334ZM3.74081 4.23859L6.14658 3.57098C6.23504 4.32137 6.74876 5.02122 7.68771 5.18457C8.20587 5.27472 8.618 5.66939 8.73047 6.18316L8.74966 6.27086C8.75385 6.29001 8.75827 6.30897 8.7629 6.32775L4.39526 7.54098V11.7936L4.39531 11.8089C4.39531 13.0172 3.41576 13.9968 2.20742 13.9968C0.999084 13.9968 0.0195312 13.0172 0.0195312 11.8089C0.0195312 10.6005 0.999084 9.62098 2.20742 9.62098C2.44772 9.62098 2.67897 9.65972 2.89526 9.73129V6.97092V5.52396V5.3433V5.33909C2.89667 5.08786 2.98051 4.84404 3.13389 4.64507C3.28653 4.44705 3.49965 4.30433 3.74081 4.23859ZM11.5475 7.51205C11.052 7.79034 10.4405 7.831 9.9109 7.6349C9.73475 7.58917 9.54998 7.56483 9.35952 7.56483C8.15118 7.56483 7.17163 8.54438 7.17163 9.75272C7.17163 10.9611 8.15118 11.9406 9.35952 11.9406C10.5216 11.9406 11.472 11.0348 11.5432 9.89069C11.546 9.86401 11.5475 9.83691 11.5475 9.80946V7.51205Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189568">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189547)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.53125 0.00402832C1.13342 0.00402832 0.751895 0.162064 0.47059 0.443368C0.189286 0.724673 0.03125 1.1062 0.03125 1.50403V9.50403C0.03125 9.90185 0.189286 10.2834 0.47059 10.5647C0.751894 10.846 1.13342 11.004 1.53125 11.004H5.46229C5.27732 10.3876 5.37222 9.6894 5.74699 9.15674H2.87063C2.70736 9.15674 2.57501 9.02438 2.57501 8.86112C2.57501 7.22845 3.89855 5.90491 5.53122 5.90491C6.6697 5.90491 7.65786 6.54847 8.15177 7.49165C8.18522 7.41307 8.21189 7.33079 8.23106 7.24548L8.25393 7.1411C8.55971 5.74422 9.9475 5.23971 11.0313 5.63363V1.50403C11.0313 1.1062 10.8732 0.724673 10.5919 0.443368C10.3106 0.162064 9.92907 0.00402832 9.53125 0.00402832H1.53125ZM5.53122 5.02293C6.51082 5.02293 7.30494 4.22881 7.30494 3.2492C7.30494 2.2696 6.51082 1.47548 5.53122 1.47548C4.55162 1.47548 3.75749 2.2696 3.75749 3.2492C3.75749 4.22881 4.55162 5.02293 5.53122 5.02293ZM9.47501 7.4084C9.66516 6.5398 10.902 6.5344 11.0997 7.4013L11.1095 7.44453C11.1162 7.47417 11.1225 7.5021 11.1293 7.53074C11.3758 8.58063 12.2254 9.38286 13.289 9.5679C14.1942 9.72537 14.1942 11.0247 13.289 11.1822C12.2198 11.3682 11.3667 12.178 11.1254 13.2362L11.0997 13.3488C10.902 14.2157 9.66515 14.2103 9.47501 13.3417L9.45386 13.245C9.22135 12.1829 8.36933 11.3669 7.2981 11.1806C6.39477 11.0234 6.39477 9.72665 7.2981 9.5695C8.3656 9.38378 9.21542 8.57287 9.45141 7.51612L9.46712 7.44457L9.47501 7.4084Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189547">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189532)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.91814 0.731127C10.0968 -0.0852602 11.2593 -0.0903417 11.4451 0.724453L11.4535 0.761616C11.4591 0.786719 11.4645 0.809825 11.4701 0.83394C11.6726 1.69644 12.3707 2.35548 13.2444 2.50749C14.0964 2.65572 14.0964 3.87884 13.2444 4.02707C12.366 4.17989 11.6652 4.84516 11.4669 5.71445L11.4451 5.81011C11.2593 6.62491 10.0968 6.61982 9.91814 5.80344L9.90017 5.72136C9.7092 4.84895 9.0094 4.17879 8.12955 4.02572C7.27905 3.87775 7.27904 2.65681 8.12955 2.50885C9.00633 2.35631 9.70432 1.69027 9.89815 0.822322L9.91137 0.762143L9.91814 0.731127ZM1.10418 0.170359C0.564475 0.170359 0.126953 0.60788 0.126953 1.14759V5.30082C0.126953 5.84053 0.564475 6.27805 1.10418 6.27805H5.25742C5.79713 6.27805 6.23465 5.84053 6.23465 5.30082V1.14759C6.23465 0.60788 5.79713 0.170359 5.25742 0.170359H1.10418ZM0.126953 8.74214C0.126953 8.20243 0.564475 7.7649 1.10418 7.7649H5.25742C5.79713 7.7649 6.23465 8.20243 6.23465 8.74214V12.8954C6.23465 13.4351 5.79713 13.8726 5.25742 13.8726H1.10418C0.564475 13.8726 0.126953 13.4351 0.126953 12.8954V8.74214ZM7.72149 8.74214C7.72149 8.20243 8.15901 7.7649 8.69872 7.7649H12.8519C13.3917 7.7649 13.8292 8.20243 13.8292 8.74214V12.8954C13.8292 13.4351 13.3917 13.8726 12.8519 13.8726H8.69872C8.15901 13.8726 7.72149 13.4351 7.72149 12.8954V8.74214Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189532">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189550)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.95758 0.658957C10.1371 -0.161122 11.3048 -0.166226 11.4915 0.652253L11.5 0.689857C11.5058 0.715279 11.5111 0.738717 11.5168 0.76315C11.7224 1.6389 12.4312 2.30807 13.3183 2.46241C14.1741 2.6113 14.1741 3.8398 13.3183 3.98869C12.4264 4.14385 11.7149 4.81934 11.5135 5.70199L11.4915 5.79885C11.3048 6.61733 10.1371 6.61222 9.95758 5.79214L9.93939 5.70903C9.74548 4.82321 9.03492 4.14274 8.14153 3.98731C7.2873 3.8387 7.28729 2.6124 8.14153 2.46379C9.0318 2.3089 9.74053 1.63262 9.93734 0.75132L9.95073 0.69036L9.95758 0.658957ZM7.96919 4.97763C6.31885 4.69052 6.05472 2.65454 7.17687 1.80769C6.59157 1.58233 5.95572 1.4588 5.29102 1.4588C2.39152 1.4588 0.0410156 3.80931 0.0410156 6.7088C0.0410156 8.49682 0.993353 10.2936 2.05776 11.6161C2.5954 12.284 3.17925 12.8534 3.72099 13.2608C3.99167 13.4643 4.26002 13.6336 4.5139 13.7539C4.76038 13.8707 5.02931 13.9588 5.29102 13.9588C5.55272 13.9588 5.82165 13.8707 6.06813 13.7539C6.32201 13.6336 6.59036 13.4643 6.86104 13.2608C7.40278 12.8534 7.98663 12.284 8.52427 11.6161C9.45118 10.4644 10.2931 8.95312 10.4951 7.40113C9.80485 7.31671 9.16041 6.85142 8.97558 6.0071L8.95739 5.92398C8.85081 5.43709 8.46024 5.06306 7.96919 4.97763ZM5.29102 8.4588C6.25751 8.4588 7.04102 7.6753 7.04102 6.7088C7.04102 5.7423 6.25751 4.9588 5.29102 4.9588C4.32452 4.9588 3.54102 5.7423 3.54102 6.7088C3.54102 7.6753 4.32452 8.4588 5.29102 8.4588Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189550">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189559)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.81502 0.666197C9.9981 -0.170135 11.189 -0.175341 11.3793 0.65936L11.3883 0.698818C11.3944 0.725719 11.4 0.75057 11.4061 0.776468C11.6254 1.71056 12.3814 2.4243 13.3276 2.58893C14.2 2.74069 14.2 3.99292 13.3276 4.14468C12.3763 4.31019 11.6174 5.03067 11.4026 5.97212L11.3793 6.07425C11.189 6.90896 9.99809 6.90374 9.81502 6.06741L9.79583 5.97977C9.58899 5.03487 8.83104 4.30902 7.87808 4.14323C7.0074 3.99176 7.00739 2.74185 7.87808 2.59038C8.82772 2.42516 9.58371 1.70378 9.79365 0.763705L9.80781 0.699221L9.81502 0.666197ZM7.69172 5.21435C5.81589 4.88801 5.63841 2.41899 7.1594 1.68728C7.0236 1.44232 6.87994 1.20164 6.72859 0.965667C6.64978 0.962837 6.57062 0.961411 6.49113 0.961411C6.41169 0.961411 6.33258 0.962835 6.25382 0.965661C5.12844 2.72022 4.42868 4.7349 4.22016 6.83759H8.76225C8.7325 6.53763 8.69276 6.23947 8.64321 5.9436C8.46716 5.56473 8.11481 5.28796 7.69172 5.21435ZM0.0195433 6.83765C0.274572 4.16392 2.14899 1.96256 4.65082 1.22555C3.72044 2.95001 3.14382 4.86059 2.96455 6.83759H0.0284221L0.0195433 6.83765ZM0.0284221 8.08759H2.96451C3.1437 10.0646 3.72025 11.9752 4.65057 13.6997C2.14882 12.9626 0.274503 10.7612 0.0195312 8.08752L0.0284221 8.08759ZM4.22011 8.08759C4.42855 10.1903 5.12824 12.205 6.25357 13.9596C6.3324 13.9625 6.4116 13.9639 6.49113 13.9639C6.57071 13.9639 6.64996 13.9625 6.72885 13.9596C7.85417 12.205 8.55386 10.1903 8.7623 8.08759H4.22011ZM12.9628 8.08753C12.7078 10.7612 10.8336 12.9625 8.33187 13.6996C9.26217 11.9751 9.83871 10.0646 10.0179 8.08759H12.9542L12.9628 8.08753Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189559">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189565)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.72096 0.656606C9.90611 -0.189194 11.1104 -0.194459 11.3029 0.649692L11.3122 0.690284C11.3184 0.718022 11.3243 0.743687 11.3305 0.770418C11.5578 1.73849 12.3413 2.4782 13.322 2.64882C14.2039 2.80225 14.2039 4.06831 13.322 4.22174C12.3361 4.39327 11.5495 5.13997 11.3269 6.11566L11.3029 6.22087C11.1104 7.06502 9.90611 7.05975 9.72096 6.21395L9.7012 6.12368C9.48682 5.14436 8.70127 4.39208 7.7136 4.22025C6.83334 4.06711 6.83333 2.80345 7.7136 2.65031C8.69783 2.47908 9.48135 1.73142 9.69894 0.757111L9.71355 0.690565L9.72096 0.656606ZM1.76562 2.48053H6.15635C5.58627 3.53175 6.04052 5.08121 7.51904 5.33843C8.00577 5.42311 8.40161 5.76882 8.55511 6.23053H1.51562V12.2305C1.51562 12.3686 1.62755 12.4805 1.76562 12.4805H10.7656C10.9037 12.4805 11.0156 12.3686 11.0156 12.2305V7.92384C11.6597 7.76082 12.226 7.27774 12.4095 6.47325L12.4335 6.36805C12.4531 6.2821 12.4807 6.19943 12.5156 6.12083V12.2305C12.5156 13.197 11.7321 13.9805 10.7656 13.9805H1.76562C0.799126 13.9805 0.015625 13.197 0.015625 12.2305V5.48053V4.23053C0.015625 3.26404 0.799127 2.48053 1.76562 2.48053ZM2.72871 7.31716C2.97279 7.07308 3.36852 7.07308 3.61259 7.31716L5.11259 8.81716C5.35667 9.06123 5.35667 9.45696 5.11259 9.70104L3.61259 11.201C3.36852 11.4451 2.97279 11.4451 2.72871 11.201C2.48463 10.957 2.48463 10.5612 2.72871 10.3172L3.78677 9.2591L2.72871 8.20104C2.48463 7.95696 2.48463 7.56123 2.72871 7.31716ZM5.54565 10.7591C5.54565 10.4139 5.82547 10.1341 6.17065 10.1341H7.67065C8.01583 10.1341 8.29565 10.4139 8.29565 10.7591C8.29565 11.1043 8.01583 11.3841 7.67065 11.3841H6.17065C5.82547 11.3841 5.54565 11.1043 5.54565 10.7591Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189565">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189538)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.2127 2.53752C8.91903 1.6074 7.23449 1.24494 5.57696 1.68908C2.78152 2.43811 1.06874 5.21231 1.5948 8.0098C1.67134 8.41688 1.4034 8.80893 0.996318 8.88548C0.58924 8.96203 0.197182 8.69408 0.120633 8.28701C-0.549251 4.72465 1.62993 1.19377 5.18873 0.240189C7.38469 -0.348217 9.61568 0.176234 11.2842 1.46596L11.8965 0.853736C12.2114 0.538754 12.75 0.761838 12.75 1.20729V3.50018C12.75 3.77633 12.5262 4.00018 12.25 4.00018H9.95712C9.51167 4.00018 9.28858 3.46161 9.60357 3.14663L10.2127 2.53752ZM13.0041 5.11487C13.4111 5.03832 13.8032 5.30626 13.8797 5.71334C14.5496 9.2757 12.3705 12.8066 8.81165 13.7602C6.61562 14.3486 4.38455 13.8241 2.71598 12.5343L2.10362 13.1466C1.78863 13.4616 1.25006 13.2385 1.25006 12.7931V10.5002C1.25006 10.224 1.47392 10.0002 1.75006 10.0002H4.04296C4.48841 10.0002 4.71149 10.5387 4.39651 10.8537L3.78752 11.4627C5.08119 12.3929 6.76582 12.7554 8.42342 12.3113C11.2189 11.5622 12.9316 8.78804 12.4056 5.99055C12.329 5.58347 12.597 5.19142 13.0041 5.11487ZM6.18098 4.03352C6.37112 3.16492 7.60789 3.15952 7.80562 4.02642L7.81543 4.06966C7.82216 4.09938 7.82845 4.12715 7.8352 4.15586C8.0817 5.20576 8.93138 6.00799 9.99498 6.19302C10.9001 6.35049 10.9001 7.64983 9.99498 7.8073C8.9257 7.99332 8.07265 8.80313 7.8313 9.86129L7.80562 9.9739C7.60789 10.8408 6.37112 10.8354 6.18098 9.9668L6.15983 9.87017C5.92731 8.808 5.0753 7.99207 4.00407 7.8057C3.10074 7.64855 3.10073 6.35177 4.00407 6.19462C5.07157 6.0089 5.92138 5.19799 6.15738 4.14124L6.17309 4.0697L6.18098 4.03352Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189538">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189553)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.82494 0.638365C10.0081 -0.197968 11.1989 -0.203173 11.3893 0.631528L11.3982 0.670986C11.4043 0.697887 11.4099 0.722738 11.416 0.748636C11.6353 1.68273 12.3913 2.39647 13.3376 2.5611C14.2099 2.71285 14.2099 3.96509 13.3376 4.11685C12.3863 4.28236 11.6273 5.00284 11.4126 5.94428L11.3893 6.04642C11.1989 6.88112 10.0081 6.87591 9.82494 6.03958L9.80576 5.95194C9.59891 5.00704 8.84097 4.28119 7.888 4.1154C7.01732 3.96393 7.01731 2.71402 7.888 2.56254C8.83765 2.39733 9.59364 1.67595 9.80357 0.735873L9.81774 0.671389L9.82494 0.638365ZM10.552 7.75606C10.0846 7.74291 9.6217 7.56639 9.27655 7.22701C9.23495 7.2547 9.1957 7.28688 9.15956 7.32342C9.13146 7.35171 9.10599 7.38184 9.08322 7.41349C8.6335 8.00912 8.10529 8.6178 7.50979 9.2133C7.05819 9.6649 6.60047 10.0764 6.14797 10.4436C5.69549 10.0764 5.23777 9.6649 4.78618 9.21331C4.33458 8.76172 3.9231 8.304 3.55583 7.8515C3.9231 7.39901 4.33458 6.94128 4.78618 6.48969C5.38145 5.89442 5.98989 5.36639 6.5853 4.91678C6.61928 4.89238 6.65154 4.86488 6.68162 4.83435C6.70948 4.80622 6.73472 4.77627 6.75731 4.74484C6.36168 4.39279 6.15864 3.88856 6.1482 3.38032C5.16669 2.69292 4.1965 2.18363 3.33055 1.91885C2.39231 1.63196 1.29292 1.55452 0.571954 2.27548C0.104576 2.74286 -0.0227025 3.37911 0.0136461 3.98383C0.0500648 4.58972 0.252331 5.25637 0.559417 5.93196C0.836935 6.5425 1.21427 7.19128 1.67665 7.8515C1.21427 8.51172 0.836935 9.1605 0.559417 9.77104C0.252331 10.4466 0.050065 11.1133 0.0136462 11.7192C-0.0227026 12.3239 0.104576 12.9601 0.571954 13.4275C1.03934 13.8949 1.67559 14.0222 2.28031 13.9858C2.88619 13.9494 3.55284 13.7471 4.22843 13.4401C4.83897 13.1625 5.48775 12.7852 6.14797 12.3228C6.8082 12.7852 7.45699 13.1625 8.06754 13.4401C8.74312 13.7472 9.40978 13.9494 10.0157 13.9858C10.6204 14.0222 11.2567 13.8949 11.7241 13.4275C12.445 12.7066 12.3676 11.6072 12.0807 10.6689C11.8073 9.77499 11.2734 8.76995 10.552 7.75606ZM2.89194 3.35329C3.46733 3.52922 4.15481 3.86372 4.89904 4.34669C4.505 4.68092 4.11194 5.04261 3.72552 5.42903C3.34 5.81454 2.97818 6.2076 2.64318 6.60257C2.34978 6.15049 2.10883 5.71573 1.92497 5.31125C1.66137 4.73134 1.53244 4.25134 1.51095 3.89383C1.48939 3.53516 1.5775 3.39126 1.63262 3.33614C1.7228 3.24596 2.0555 3.09753 2.89194 3.35329ZM1.92497 10.3917C2.10883 9.98727 2.34978 9.55251 2.64317 9.10043C2.97818 9.4954 3.34 9.88846 3.72552 10.274C4.11103 10.6595 4.50408 11.0213 4.89904 11.3563C4.44696 11.6497 4.01221 11.8906 3.60773 12.0745C3.02782 12.3381 2.54782 12.467 2.19031 12.4885C1.83163 12.5101 1.68774 12.422 1.63262 12.3669C1.5775 12.3117 1.48939 12.1678 1.51095 11.8092C1.53244 11.4517 1.66137 10.9717 1.92497 10.3917ZM8.68824 12.0745C8.28375 11.8907 7.84899 11.6497 7.3969 11.3563C7.79187 11.0213 8.18493 10.6595 8.57045 10.274C8.95687 9.88754 9.31856 9.49448 9.65279 9.10043C10.1358 9.84467 10.4703 10.5322 10.6462 11.1075C10.902 11.944 10.7536 12.2767 10.6634 12.3669C10.6083 12.422 10.4644 12.5101 10.1057 12.4885C9.74815 12.4671 9.26815 12.3381 8.68824 12.0745ZM5.14795 7.8519C5.14795 7.29961 5.59567 6.8519 6.14795 6.8519C6.70024 6.8519 7.14795 7.29961 7.14795 7.8519C7.14795 8.40418 6.70024 8.8519 6.14795 8.8519C5.59567 8.8519 5.14795 8.40418 5.14795 7.8519Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189553">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189571)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.80531 0.745416C9.98605 -0.0802161 11.1616 -0.0853549 11.3495 0.738666L11.3582 0.776877C11.364 0.802571 11.3696 0.826946 11.3754 0.851685C11.5857 1.74737 12.3105 2.43176 13.2179 2.58962C14.0793 2.73949 14.0793 3.9761 13.2179 4.12597C12.3057 4.28467 11.5779 4.97553 11.372 5.87826L11.3495 5.97692C11.1616 6.80095 9.98605 6.7958 9.80531 5.97017L9.78678 5.88551C9.58845 4.97951 8.8617 4.28353 7.94797 4.12457C7.08811 3.97498 7.0881 2.74061 7.94797 2.59102C8.85852 2.43261 9.58339 1.74092 9.78469 0.839537L9.79834 0.777362L9.80531 0.745416ZM7.77086 5.14249C6.42908 4.90906 5.98687 3.54072 6.44426 2.55925C6.40716 2.52258 6.36673 2.48909 6.32335 2.45924C6.17579 2.35769 6.001 2.30307 5.82187 2.30254H5.0927C4.91357 2.30307 4.73878 2.35769 4.59121 2.45924C4.44365 2.56079 4.33019 2.70453 4.26571 2.87165L3.88334 3.8587L2.56727 4.60566L1.51797 4.4456C1.34325 4.42188 1.16543 4.45064 1.00709 4.52823C0.848761 4.60581 0.717065 4.72872 0.628743 4.88132L0.273049 5.50379C0.181904 5.65882 0.139911 5.83785 0.152611 6.01725C0.165312 6.19664 0.232109 6.36797 0.344188 6.50862L1.01111 7.33561V8.82952L0.361973 9.65651C0.249894 9.79716 0.183096 9.96849 0.170396 10.1479C0.157695 10.3273 0.199689 10.5063 0.290834 10.6613L0.646528 11.2838C0.73485 11.4364 0.866546 11.5593 1.02488 11.6369C1.18321 11.7145 1.36104 11.7433 1.53576 11.7195L2.58506 11.5595L3.88334 12.3064L4.26571 13.2935C4.33019 13.4606 4.44365 13.6044 4.59121 13.7059C4.73878 13.8074 4.91357 13.8621 5.0927 13.8626H5.83965C6.01878 13.8621 6.19357 13.8074 6.34114 13.7059C6.4887 13.6044 6.60216 13.4606 6.66664 13.2935L7.04901 12.3064L8.3473 11.5595L9.39659 11.7195C9.57131 11.7433 9.74914 11.7145 9.90747 11.6369C10.0658 11.5593 10.1975 11.4364 10.2858 11.2838L10.6415 10.6613C10.7326 10.5063 10.7746 10.3273 10.7619 10.1479C10.7492 9.96849 10.6824 9.79716 10.5703 9.65651L9.90346 8.82952V7.50279C9.38145 7.30485 8.94401 6.86732 8.79599 6.19111L8.77746 6.10646C8.66889 5.6105 8.27105 5.22951 7.77086 5.14249ZM5.45729 9.94043C6.48336 9.94043 7.31515 9.10864 7.31515 8.08257C7.31515 7.0565 6.48336 6.2247 5.45729 6.2247C4.43122 6.2247 3.59942 7.0565 3.59942 8.08257C3.59942 9.10864 4.43122 9.94043 5.45729 9.94043Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189571">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189583)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.52734 9.33109C8.10835 8.99499 8.61545 8.54534 9.0183 8.01248C8.89486 7.82421 8.80049 7.6037 8.74516 7.35097L8.72598 7.26333C8.61359 6.74991 8.20174 6.3555 7.68394 6.26542C5.61213 5.90498 5.61208 2.93078 7.68394 2.57033C8.18485 2.48319 8.58661 2.11125 8.71402 1.62232C7.79996 0.625182 6.48666 0 5.02734 0C2.26592 0 0.0273438 2.23858 0.0273438 5C0.0273438 6.85071 1.03284 8.46657 2.52734 9.33109V10.5C2.52734 11.0523 2.97506 11.5 3.52734 11.5H6.52734C7.07963 11.5 7.52734 11.0523 7.52734 10.5V9.33109ZM2.52734 13.25C2.52734 12.8358 2.86313 12.5 3.27734 12.5H6.77734C7.19155 12.5 7.52734 12.8358 7.52734 13.25C7.52734 13.6642 7.19155 14 6.77734 14H3.27734C2.86313 14 2.52734 13.6642 2.52734 13.25ZM9.80723 1.71727C9.99031 0.880936 11.1811 0.87573 11.3715 1.71043L11.3805 1.74989C11.3865 1.77679 11.3922 1.80164 11.3983 1.82754C11.6176 2.76163 12.3735 3.47537 13.3198 3.64C14.1921 3.79176 14.1921 5.04399 13.3198 5.19575C12.3685 5.36126 11.6095 6.08174 11.3948 7.02319L11.3715 7.12532C11.1811 7.96003 9.99031 7.95481 9.80723 7.11848L9.78805 7.03085C9.58121 6.08595 8.82326 5.36009 7.8703 5.19431C6.99962 5.04283 6.99961 3.79292 7.8703 3.64145C8.81994 3.47624 9.57593 2.75485 9.78587 1.81478L9.80003 1.75029L9.80723 1.71727Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189583">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189574)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.5 1.5C3.73858 1.5 1.5 3.73858 1.5 6.5C1.5 9.26142 3.73858 11.5 6.5 11.5C9.26142 11.5 11.5 9.26142 11.5 6.5C11.5 3.73858 9.26142 1.5 6.5 1.5ZM0 6.5C0 2.91015 2.91015 0 6.5 0C10.0899 0 13 2.91015 13 6.5C13 7.9341 12.5356 9.25973 11.7489 10.3347L13.7073 12.2931C14.0979 12.6836 14.0979 13.3168 13.7073 13.7073C13.3168 14.0978 12.6837 14.0978 12.2931 13.7073L10.3347 11.7489C9.25974 12.5356 7.9341 13 6.5 13C2.91015 13 0 10.0899 0 6.5ZM5.73911 3.86951C5.91933 3.04626 7.09153 3.04113 7.27893 3.86278L7.28755 3.90071C7.29338 3.92646 7.29874 3.95014 7.30455 3.97489C7.51284 4.86203 8.2308 5.5399 9.12952 5.69625C9.98853 5.84569 9.98853 7.07883 9.12952 7.22828C8.22601 7.38546 7.5052 8.06974 7.30126 8.96386L7.27893 9.06175C7.09153 9.8834 5.91933 9.87827 5.73911 9.05502L5.72073 8.97102C5.52429 8.07367 4.80448 7.38434 3.89947 7.22689C3.04202 7.07772 3.04201 5.84681 3.89947 5.69764C4.80133 5.54074 5.51928 4.85565 5.71865 3.96288L5.73219 3.90124L5.73911 3.86951Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189574">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189577)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.81542 0.696227C9.99849 -0.140106 11.1893 -0.145312 11.3797 0.689389L11.3887 0.728847C11.3948 0.755748 11.4004 0.7806 11.4065 0.806498C11.6258 1.74059 12.3817 2.45433 13.328 2.61896C14.2003 2.77072 14.2003 4.02295 13.328 4.17471C12.3767 4.34022 11.6177 5.0607 11.403 6.00215L11.3797 6.10428C11.1893 6.93899 9.99849 6.93377 9.81542 6.09744L9.79623 6.0098C9.58939 5.0649 8.83144 4.33905 7.87848 4.17326C7.0078 4.02179 7.00779 2.77188 7.87848 2.62041C8.82812 2.45519 9.58411 1.73381 9.79405 0.793734L9.80821 0.729251L9.81542 0.696227ZM7.69212 5.24438C6.99023 5.12227 6.52613 4.70018 6.2998 4.18135H4.78172C4.45951 4.16762 4.14131 4.25811 3.87441 4.4396C3.60355 4.62379 3.40043 4.89156 3.29606 5.20203L3.29545 5.20385L2.63913 7.18135H1.5C1.10217 7.18135 0.720644 7.33939 0.43934 7.62069C0.158035 7.902 0 8.28353 0 8.68135V10.6814C0 11.0792 0.158035 11.4607 0.43934 11.742C0.603998 11.9067 0.802996 12.0291 1.0195 12.1023C1.18121 10.8772 2.22965 9.9317 3.49892 9.9317C4.79537 9.9317 5.86142 10.9181 5.98751 12.1814H8.01035C8.13644 10.9181 9.20249 9.9317 10.4989 9.9317C11.7684 9.9317 12.817 10.8776 12.9784 12.103C13.1957 12.0299 13.3955 11.9072 13.5607 11.742C13.842 11.4607 14 11.0792 14 10.6814V8.68135C14 8.28353 13.842 7.902 13.5607 7.62069C13.2794 7.33939 12.8978 7.18135 12.5 7.18135H12.0266C11.101 8.25165 9.11102 7.96388 8.75335 6.32993L8.73416 6.24229C8.62177 5.72887 8.20993 5.33446 7.69212 5.24438ZM3.49894 13.9338C2.66993 13.9338 1.99788 13.2618 1.99788 12.4328C1.99788 11.6037 2.66993 10.9317 3.49894 10.9317C4.32795 10.9317 5 11.6037 5 12.4328C5 13.2618 4.32795 13.9338 3.49894 13.9338ZM10.4989 13.9338C9.66993 13.9338 8.99789 13.2618 8.99789 12.4328C8.99789 11.6037 9.66993 10.9317 10.4989 10.9317C11.328 10.9317 12 11.6037 12 12.4328C12 13.2618 11.328 13.9338 10.4989 13.9338Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189577">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_189580)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.739 0.850524C9.92208 0.0141907 11.1129 0.00898498 11.3033 0.843686L11.3122 0.883144C11.3182 0.910044 11.324 0.934897 11.33 0.960795C11.5494 1.89488 12.3053 2.60863 13.2516 2.77326C14.1239 2.92501 14.1239 4.17725 13.2516 4.32901C12.3003 4.49451 11.5413 5.215 11.3266 6.15644L11.3033 6.25858C11.1129 7.09328 9.92208 7.08807 9.739 6.25174L9.71982 6.1641C9.51297 5.2192 8.75502 4.49335 7.80206 4.32756C6.93138 4.17609 6.93137 2.92618 7.80206 2.7747C8.75171 2.60949 9.5077 1.88811 9.71763 0.948031L9.73179 0.883548L9.739 0.850524ZM8.65781 6.39659C8.54608 5.88622 8.13846 5.49346 7.625 5.40032V12.6147C8.17306 13.3232 9.03161 13.7795 9.99672 13.7795C11.3078 13.7795 12.4222 12.9375 12.8287 11.7649C12.582 11.7422 12.316 11.6821 12.0507 11.5823C11.4556 11.3583 10.8088 10.9168 10.2781 10.1463C10.0824 9.86206 10.1541 9.47289 10.4384 9.2771C10.7226 9.08131 11.1118 9.15304 11.3076 9.43731C11.6897 9.99209 12.1284 10.2759 12.491 10.4124C12.6748 10.4815 12.8392 10.5129 12.9672 10.5221L12.9899 10.5235C13.6095 9.85129 14 8.54648 14 7.45712C14 6.72292 13.8226 5.99874 13.5173 5.38485C13.4912 5.39037 13.4648 5.39546 13.438 5.40012C12.9184 5.49052 12.5039 5.88402 12.3866 6.39821L12.3633 6.50034C11.9147 8.46738 9.10842 8.45506 8.67699 6.48423L8.65781 6.39659ZM6.375 4.61469C5.95752 3.98445 5.95752 3.11779 6.375 2.48756V1.96736C5.89476 1.40406 5.17994 1.04684 4.3817 1.04684C2.93566 1.04684 1.76342 2.21908 1.76342 3.66511L1.76343 3.66791C1.8018 4.05742 1.96105 4.34866 2.1451 4.55835C2.37401 4.81912 2.61517 4.92556 2.67236 4.93859C3.00891 5.01532 3.21953 5.35034 3.1428 5.68688C3.06608 6.02343 2.73106 6.23405 2.39451 6.15732C2.03531 6.07543 1.57457 5.80322 1.20568 5.38295C1.06415 5.22172 0.932208 5.03436 0.821827 4.8212C0.312464 5.52594 0 6.4827 0 7.45712C0 8.1919 0.177677 9.02469 0.483473 9.69526C0.563842 9.60565 0.650146 9.52334 0.742325 9.45056C1.01323 9.23665 1.40626 9.28286 1.62016 9.55377C1.83407 9.82468 1.78786 10.2177 1.51695 10.4316C1.33903 10.5721 1.15327 10.8751 1.03924 11.2259C1.25336 12.6708 2.49886 13.7795 4.00331 13.7795C4.9684 13.7795 5.82694 13.3233 6.375 12.6147V8.9453C6.00658 9.24099 5.54184 9.46587 4.9699 9.5297C4.62685 9.56799 4.31772 9.32094 4.27943 8.97789C4.24114 8.63484 4.4882 8.32571 4.83125 8.28742C5.33987 8.23065 5.70976 7.9383 5.97421 7.55267C6.24167 7.16265 6.35817 6.73542 6.375 6.53035V4.61469Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_189580">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_187973)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 1.53812C0 0.696591 0.694014 0 1.53623 0H12.4638C13.306 0 14 0.69659 14 1.53812V4.46371C14 8.72248 11.4311 12.394 7.57577 13.8884C7.23129 14.0497 6.81222 14.0195 6.46482 13.9035L6.45284 13.8993L6.44342 13.8958C2.57482 12.4052 0 8.63232 0 4.46371V1.53812ZM3.65545 5.1308C4.01637 4.86333 5.27972 4.22144 6.99761 4.22144C8.7155 4.22144 9.97886 4.86333 10.3398 5.1308C10.6171 5.33632 11.0085 5.27811 11.214 5.00078C11.4196 4.72346 11.3614 4.33203 11.084 4.12651C10.5164 3.70583 8.99401 2.97144 6.99761 2.97144C5.00122 2.97144 3.47886 3.70583 2.9112 4.12651C2.63387 4.33203 2.57566 4.72346 2.78118 5.00078C2.9867 5.27811 3.37813 5.33632 3.65545 5.1308ZM6.99758 6.80493C6.20195 6.80493 5.28515 7.18896 4.93419 7.38403C4.63248 7.55172 4.25196 7.44308 4.08426 7.14137C3.91657 6.83966 4.02521 6.45914 4.32692 6.29145C4.76497 6.04798 5.89959 5.55493 6.99758 5.55493C8.09557 5.55493 9.2302 6.04798 9.66824 6.29145C9.96995 6.45914 10.0786 6.83966 9.9109 7.14137C9.74321 7.44308 9.36269 7.55172 9.06098 7.38403C8.71001 7.18896 7.79322 6.80493 6.99758 6.80493ZM6.52604 9.62531C6.55379 9.60989 6.63259 9.57198 6.73436 9.53909C6.83817 9.50555 6.93116 9.48859 6.9976 9.48859C7.06405 9.48859 7.15704 9.50555 7.26085 9.53909C7.36262 9.57198 7.44142 9.60989 7.46916 9.62531C7.77087 9.793 8.15139 9.68436 8.31908 9.38265C8.48678 9.08094 8.37813 8.70042 8.07643 8.53273C7.90369 8.43672 7.45886 8.23859 6.9976 8.23859C6.53635 8.23859 6.09152 8.43672 5.91878 8.53273C5.61707 8.70042 5.50843 9.08094 5.67612 9.38265C5.84381 9.68436 6.22434 9.793 6.52604 9.62531Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_187973">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_188033)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.316406 0.816406C0.316406 0.540264 0.540264 0.316406 0.816406 0.316406H13.1836C13.4597 0.316406 13.6836 0.540264 13.6836 0.816406V13.1836C13.6836 13.4597 13.4597 13.6836 13.1836 13.6836H0.816406C0.540264 13.6836 0.316406 13.4597 0.316406 13.1836V0.816406ZM1.36923 12.6303L5.39103 1.36926H8.60847L12.6303 12.6303H9.70935L6.99975 5.14708L5.32216 10.2172H6.87834L7.6827 12.6303H1.36923Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_188033">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 704 B |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_188036)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.43934 0.43934C0.720644 0.158035 1.10218 0 1.5 0H12.5C12.8978 0 13.2794 0.158035 13.5607 0.43934C13.842 0.720644 14 1.10217 14 1.5V11.75V12.5C14 12.8978 13.842 13.2794 13.5607 13.5607C13.2794 13.842 12.8978 14 12.5 14H1.5C1.10217 14 0.720644 13.842 0.43934 13.5607C0.158035 13.2794 0 12.8978 0 12.5V11.5V1.5C0 1.10218 0.158035 0.720644 0.43934 0.43934ZM12.295 5.65853C12.6402 5.65853 12.92 5.37871 12.92 5.03353C12.92 4.68835 12.6402 4.40853 12.295 4.40853H9.67309C9.32791 4.40853 9.04809 4.68835 9.04809 5.03353C9.04809 5.37871 9.32791 5.65853 9.67309 5.65853H10.3591V8.96646C10.3591 9.31164 10.6389 9.59146 10.9841 9.59146C11.3292 9.59146 11.6091 9.31164 11.6091 8.96646V5.65853H12.295ZM6.72339 4.40853C7.06857 4.40853 7.34839 4.68835 7.34839 5.03353V8.34147H9.0176C9.36278 8.34147 9.6426 8.62129 9.6426 8.96647C9.6426 9.31164 9.36278 9.59147 9.0176 9.59147H6.72339C6.37821 9.59147 6.09839 9.31164 6.09839 8.96647V5.03353C6.09839 4.68835 6.37821 4.40853 6.72339 4.40853ZM4.36799 5.14681C4.22103 4.70592 3.80842 4.40853 3.34368 4.40853C2.87893 4.40853 2.46633 4.70592 2.31937 5.14681L1.55363 7.44401C1.55037 7.45308 1.54731 7.46225 1.54446 7.47152L1.11203 8.76882C1.00287 9.09629 1.17985 9.45024 1.50731 9.55939C1.83478 9.66855 2.18873 9.49157 2.29788 9.16411L2.59242 8.28049H4.09493L4.38947 9.16411C4.49863 9.49157 4.85258 9.66855 5.18004 9.55939C5.50751 9.45024 5.68448 9.09629 5.57533 8.76882L5.14289 7.47152C5.14004 7.46225 5.13698 7.45308 5.13372 7.444L4.36799 5.14681ZM3.00909 7.03049L3.34368 6.02672L3.67827 7.03049H3.00909Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_188036">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_188039)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.44452 2.96142C4.44452 2.49167 4.98914 1.84375 6.02911 1.84375C7.06908 1.84375 7.6137 2.49167 7.6137 2.96142V4.08044H6.02911C4.49036 4.08044 2.94452 5.10489 2.94452 6.69812C2.94452 8.29134 4.49036 9.31579 6.02911 9.31579C6.87633 9.31579 7.72569 9.00523 8.31801 8.46018C8.55022 8.71505 8.86617 8.94921 9.2825 9.04913C9.68528 9.1458 10.0901 8.89765 10.1868 8.49487C10.2835 8.09209 10.0353 7.68722 9.63256 7.59055C9.55563 7.57209 9.44186 7.50004 9.31766 7.30584C9.2153 7.1458 9.14743 6.96484 9.1137 6.83852V6.69812V4.83044V2.96142C9.1137 1.3682 7.56786 0.34375 6.02911 0.34375C4.49036 0.34375 2.94452 1.3682 2.94452 2.96142C2.94452 3.37564 3.28031 3.71142 3.69452 3.71142C4.10874 3.71142 4.44452 3.37564 4.44452 2.96142ZM7.6137 5.58044H6.02911C4.98914 5.58044 4.44452 6.22837 4.44452 6.69812C4.44452 7.16787 4.98914 7.81579 6.02911 7.81579C7.06908 7.81579 7.6137 7.16787 7.6137 6.69812V6.69691V5.58044ZM0.284297 10.0497C0.562563 9.74292 1.03687 9.71977 1.34369 9.99804C2.284 10.8508 4.47445 11.7654 7.31239 11.6268C9.06979 11.5411 10.4931 11.0785 11.4532 10.6292C11.1619 10.6034 10.8883 10.6211 10.6654 10.7014C10.2756 10.8416 9.84598 10.6394 9.70574 10.2496C9.56549 9.85985 9.76776 9.43021 10.1575 9.28997C10.9503 9.0047 11.8138 9.1293 12.3973 9.27371C12.7033 9.34943 12.9664 9.43943 13.1534 9.51052C13.2475 9.54625 13.3235 9.57769 13.3776 9.60092C13.4047 9.61255 13.4264 9.62216 13.4421 9.62926L13.4613 9.638L13.4675 9.64086L13.4697 9.64189L13.471 9.64249C13.4711 9.64254 13.4712 9.64259 13.4041 9.78534C13.4041 9.78533 13.4041 9.78535 13.4041 9.78534L13.471 9.64249C13.745 9.77146 13.9148 10.0526 13.9011 10.3551L13.88 10.3542C13.9011 10.3552 13.9011 10.355 13.9011 10.3551L13.901 10.3569L13.9009 10.3593L13.9006 10.3661L13.8993 10.3872C13.8983 10.4044 13.8967 10.4281 13.8943 10.4575C13.8936 10.4665 13.8928 10.476 13.892 10.4861C13.8921 10.5373 13.887 10.5884 13.8767 10.6385C13.8746 10.6575 13.8723 10.6774 13.8697 10.6979C13.8454 10.8965 13.8023 11.1712 13.7266 11.4772C13.5822 12.0608 13.2897 12.8827 12.6676 13.451C12.3618 13.7303 11.8874 13.7089 11.608 13.403C11.3287 13.0972 11.3501 12.6228 11.6559 12.3435C11.7348 12.2714 11.8072 12.1844 11.8733 12.0858C10.7497 12.5822 9.21539 13.0357 7.38551 13.1251C4.24554 13.2783 1.63486 12.2871 0.336003 11.1091C0.0291795 10.8309 0.00602989 10.3566 0.284297 10.0497Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_188039">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1068_188042)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.5296 0.241276C3.23671 -0.0516168 2.76183 -0.0516168 2.46894 0.241276C2.17605 0.53417 2.17605 1.00904 2.46894 1.30194L3.76018 2.59318C3.14494 3.28802 2.77006 4.16189 2.6895 5.08447H11.3091C11.2285 4.16189 10.8537 3.28802 10.2384 2.59317L11.5297 1.30194C11.8226 1.00904 11.8226 0.53417 11.5297 0.241276C11.2368 -0.0516168 10.7619 -0.0516168 10.469 0.241276L9.05556 1.65468C8.4295 1.31649 7.72376 1.13477 6.99929 1.13477C6.27481 1.13477 5.56907 1.31649 4.94301 1.65468L3.5296 0.241276ZM4.1731 11.4084H9.82544V13.1597C9.82544 13.5739 10.1613 13.9097 10.5755 13.9097C10.9897 13.9097 11.3255 13.5739 11.3255 13.1597V10.666V10.6584V7.60819C11.6126 7.62061 11.8853 7.7401 12.0893 7.94413C12.3051 8.15991 12.4264 8.45258 12.4264 8.75774V9.65836C12.4264 10.0726 12.7621 10.4084 13.1764 10.4084C13.5906 10.4084 13.9264 10.0726 13.9264 9.65836V8.75774C13.9264 8.05475 13.6471 7.38055 13.15 6.88347C12.6529 6.38638 11.9787 6.10712 11.2757 6.10712H2.72289C2.0199 6.10712 1.34571 6.38638 0.848616 6.88347C0.351527 7.38055 0.0722656 8.05475 0.0722656 8.75774V9.65836C0.0722656 10.0726 0.408052 10.4084 0.822266 10.4084C1.23648 10.4084 1.57227 10.0726 1.57227 9.65836V8.75774C1.57227 8.45258 1.69349 8.15991 1.90928 7.94413C2.1133 7.74011 2.38604 7.62062 2.6731 7.60819V10.1722V10.6584V13.1597C2.6731 13.5739 3.00888 13.9097 3.4231 13.9097C3.83731 13.9097 4.1731 13.5739 4.1731 13.1597V11.4084Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1068_188042">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |