mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-07-16 05:26:11 +00:00
feat: add toggle heading in plus menu on mobile (#6784)
* chore: add logs in space bloc * feat: add toggle headings in plus menu * test: add toggle heading block test * test: toogle heading 1 block test * test: add toggle heading selection test * fix: toggle headings test * chore: update new toggle heading icons
This commit is contained in:
parent
76009613fe
commit
555d08e8ce
@ -1,6 +1,7 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'page_style_test.dart' as page_style_test;
|
||||
import 'plus_menu_test.dart' as plus_menu_test;
|
||||
import 'title_test.dart' as title_test;
|
||||
|
||||
void main() {
|
||||
@ -9,4 +10,5 @@ void main() {
|
||||
// Document integration tests
|
||||
title_test.main();
|
||||
page_style_test.main();
|
||||
plus_menu_test.main();
|
||||
}
|
||||
|
@ -0,0 +1,89 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document plus menu:', () {
|
||||
testWidgets('add the toggle heading blocks via plus menu', (tester) async {
|
||||
await tester.launchInAnonymousMode();
|
||||
await tester.createNewDocumentOnMobile('toggle heading blocks');
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
// focus on the editor
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
Selection.collapsed(Position(path: [0])),
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// open the plus menu and select the toggle heading block
|
||||
await tester.openPlusMenuAndClickButton(
|
||||
LocaleKeys.document_slashMenu_name_toggleHeading1.tr(),
|
||||
);
|
||||
|
||||
// check the block is inserted
|
||||
final block1 = editorState.getNodeAtPath([0])!;
|
||||
expect(block1.type, equals(ToggleListBlockKeys.type));
|
||||
expect(block1.attributes[ToggleListBlockKeys.level], equals(1));
|
||||
|
||||
// click the expand button won't cancel the selection
|
||||
await tester.tapButton(find.byIcon(Icons.arrow_right));
|
||||
expect(
|
||||
editorState.selection,
|
||||
equals(Selection.collapsed(Position(path: [0]))),
|
||||
);
|
||||
|
||||
// focus on the next line
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
Selection.collapsed(Position(path: [1])),
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// open the plus menu and select the toggle heading block
|
||||
await tester.openPlusMenuAndClickButton(
|
||||
LocaleKeys.document_slashMenu_name_toggleHeading2.tr(),
|
||||
);
|
||||
|
||||
// check the block is inserted
|
||||
final block2 = editorState.getNodeAtPath([1])!;
|
||||
expect(block2.type, equals(ToggleListBlockKeys.type));
|
||||
expect(block2.attributes[ToggleListBlockKeys.level], equals(2));
|
||||
|
||||
// focus on the next line
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// open the plus menu and select the toggle heading block
|
||||
await tester.openPlusMenuAndClickButton(
|
||||
LocaleKeys.document_slashMenu_name_toggleHeading3.tr(),
|
||||
);
|
||||
|
||||
// check the block is inserted
|
||||
final block3 = editorState.getNodeAtPath([2])!;
|
||||
expect(block3.type, equals(ToggleListBlockKeys.type));
|
||||
expect(block3.attributes[ToggleListBlockKeys.level], equals(3));
|
||||
|
||||
// wait a few milliseconds to ensure the selection is updated
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
// check the selection is collapsed
|
||||
expect(
|
||||
editorState.selection,
|
||||
equals(Selection.collapsed(Position(path: [2]))),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -4,8 +4,10 @@ import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.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';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_toolbar_item.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_button.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/shared/text_field/text_filed_with_metric_lines.dart';
|
||||
@ -42,6 +44,7 @@ import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import 'emoji.dart';
|
||||
import 'util.dart';
|
||||
@ -787,6 +790,57 @@ extension CommonOperations on WidgetTester {
|
||||
await tap(finder);
|
||||
await pumpAndSettle(const Duration(seconds: 2));
|
||||
}
|
||||
|
||||
/// Create a new document on mobile
|
||||
Future<void> createNewDocumentOnMobile(String name) async {
|
||||
final createPageButton = find.byKey(
|
||||
BottomNavigationBarItemType.add.valueKey,
|
||||
);
|
||||
await tapButton(createPageButton);
|
||||
expect(find.byType(MobileDocumentScreen), findsOneWidget);
|
||||
|
||||
final title = editor.findDocumentTitle('');
|
||||
expect(title, findsOneWidget);
|
||||
final textField = widget<TextField>(title);
|
||||
expect(textField.focusNode!.hasFocus, isTrue);
|
||||
|
||||
// input new name and press done button
|
||||
await enterText(title, name);
|
||||
await testTextInput.receiveAction(TextInputAction.done);
|
||||
await pumpAndSettle();
|
||||
final newTitle = editor.findDocumentTitle(name);
|
||||
expect(newTitle, findsOneWidget);
|
||||
expect(textField.controller!.text, name);
|
||||
}
|
||||
|
||||
/// Open the plus menu
|
||||
Future<void> openPlusMenuAndClickButton(String buttonName) async {
|
||||
assert(
|
||||
UniversalPlatform.isMobile,
|
||||
'This method is only supported on mobile platforms',
|
||||
);
|
||||
|
||||
final plusMenuButton = find.byKey(addBlockToolbarItemKey);
|
||||
final addMenuItem = find.byType(AddBlockMenu);
|
||||
await tapButton(plusMenuButton);
|
||||
await pumpUntilFound(addMenuItem);
|
||||
|
||||
final toggleHeading1 = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is TypeOptionMenuItem && widget.value.text == buttonName,
|
||||
);
|
||||
final scrollable = find.ancestor(
|
||||
of: find.byType(TypeOptionGridView),
|
||||
matching: find.byType(Scrollable),
|
||||
);
|
||||
await scrollUntilVisible(
|
||||
toggleHeading1,
|
||||
100,
|
||||
scrollable: scrollable,
|
||||
);
|
||||
await tapButton(toggleHeading1);
|
||||
await pumpUntilNotFound(addMenuItem);
|
||||
}
|
||||
}
|
||||
|
||||
extension SettingsFinder on CommonFinders {
|
||||
|
@ -9,12 +9,14 @@ class TypeOptionMenuItemValue<T> {
|
||||
required this.text,
|
||||
required this.backgroundColor,
|
||||
required this.onTap,
|
||||
this.iconPadding,
|
||||
});
|
||||
|
||||
final T value;
|
||||
final FlowySvgData icon;
|
||||
final String text;
|
||||
final Color backgroundColor;
|
||||
final EdgeInsets? iconPadding;
|
||||
final void Function(BuildContext context, T value) onTap;
|
||||
}
|
||||
|
||||
@ -22,7 +24,7 @@ class TypeOptionMenu<T> extends StatelessWidget {
|
||||
const TypeOptionMenu({
|
||||
super.key,
|
||||
required this.values,
|
||||
this.width = 94,
|
||||
this.width = 98,
|
||||
this.iconWidth = 72,
|
||||
this.scaleFactor = 1.0,
|
||||
this.maxAxisSpacing = 18,
|
||||
@ -39,17 +41,18 @@ class TypeOptionMenu<T> extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _GridView(
|
||||
return TypeOptionGridView(
|
||||
crossAxisCount: crossAxisCount,
|
||||
mainAxisSpacing: maxAxisSpacing * scaleFactor,
|
||||
itemWidth: width * scaleFactor,
|
||||
children: values
|
||||
.map(
|
||||
(value) => _TypeOptionMenuItem<T>(
|
||||
(value) => TypeOptionMenuItem<T>(
|
||||
value: value,
|
||||
width: width,
|
||||
iconWidth: iconWidth,
|
||||
scaleFactor: scaleFactor,
|
||||
iconPadding: value.iconPadding,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
@ -57,18 +60,21 @@ class TypeOptionMenu<T> extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _TypeOptionMenuItem<T> extends StatelessWidget {
|
||||
const _TypeOptionMenuItem({
|
||||
class TypeOptionMenuItem<T> extends StatelessWidget {
|
||||
const TypeOptionMenuItem({
|
||||
super.key,
|
||||
required this.value,
|
||||
this.width = 94,
|
||||
this.iconWidth = 72,
|
||||
this.scaleFactor = 1.0,
|
||||
this.iconPadding,
|
||||
});
|
||||
|
||||
final TypeOptionMenuItemValue<T> value;
|
||||
final double iconWidth;
|
||||
final double width;
|
||||
final double scaleFactor;
|
||||
final EdgeInsets? iconPadding;
|
||||
|
||||
double get scaledIconWidth => iconWidth * scaleFactor;
|
||||
double get scaledWidth => width * scaleFactor;
|
||||
@ -88,7 +94,8 @@ class _TypeOptionMenuItem<T> extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(24 * scaleFactor),
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.all(21 * scaleFactor),
|
||||
padding: EdgeInsets.all(21 * scaleFactor) +
|
||||
(iconPadding ?? EdgeInsets.zero),
|
||||
child: FlowySvg(
|
||||
value.icon,
|
||||
),
|
||||
@ -113,8 +120,9 @@ class _TypeOptionMenuItem<T> extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _GridView extends StatelessWidget {
|
||||
const _GridView({
|
||||
class TypeOptionGridView extends StatelessWidget {
|
||||
const TypeOptionGridView({
|
||||
super.key,
|
||||
required this.children,
|
||||
required this.crossAxisCount,
|
||||
required this.mainAxisSpacing,
|
||||
|
@ -299,11 +299,11 @@ class _TurnInfoButton extends StatelessWidget {
|
||||
} else if (type == ToggleListBlockKeys.type) {
|
||||
switch (level) {
|
||||
case 1:
|
||||
return FlowySvgs.slash_menu_icon_h1_s;
|
||||
return FlowySvgs.toggle_heading1_s;
|
||||
case 2:
|
||||
return FlowySvgs.slash_menu_icon_h2_s;
|
||||
return FlowySvgs.toggle_heading2_s;
|
||||
case 3:
|
||||
return FlowySvgs.slash_menu_icon_h3_s;
|
||||
return FlowySvgs.toggle_heading3_s;
|
||||
default:
|
||||
return FlowySvgs.slash_menu_icon_toggle_s;
|
||||
}
|
||||
|
@ -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';
|
||||
@ -19,11 +17,16 @@ 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';
|
||||
|
||||
@visibleForTesting
|
||||
const addBlockToolbarItemKey = ValueKey('add_block_toolbar_item');
|
||||
|
||||
final addBlockToolbarItem = AppFlowyMobileToolbarItem(
|
||||
itemBuilder: (context, editorState, service, __, onAction) {
|
||||
return AppFlowyMobileToolbarIconItem(
|
||||
key: addBlockToolbarItemKey,
|
||||
editorState: editorState,
|
||||
icon: FlowySvgs.m_toolbar_add_m,
|
||||
onTap: () {
|
||||
@ -75,12 +78,13 @@ Future<bool?> showAddBlockMenu(
|
||||
enableDraggableScrollable: true,
|
||||
builder: (_) => Padding(
|
||||
padding: EdgeInsets.all(16 * context.scale),
|
||||
child: _AddBlockMenu(selection: selection, editorState: editorState),
|
||||
child: AddBlockMenu(selection: selection, editorState: editorState),
|
||||
),
|
||||
);
|
||||
|
||||
class _AddBlockMenu extends StatelessWidget {
|
||||
const _AddBlockMenu({
|
||||
class AddBlockMenu extends StatelessWidget {
|
||||
const AddBlockMenu({
|
||||
super.key,
|
||||
required this.selection,
|
||||
required this.editorState,
|
||||
});
|
||||
@ -100,7 +104,32 @@ class _AddBlockMenu extends StatelessWidget {
|
||||
AppGlobals.rootNavKey.currentContext?.pop(true);
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 100),
|
||||
() => editorState.insertBlockAfterCurrentSelection(selection, node),
|
||||
() async {
|
||||
// if current selected block is a empty paragraph block, replace it with the new block.
|
||||
if (selection.isCollapsed) {
|
||||
final currentNode = editorState.getNodeAtPath(selection.end.path);
|
||||
final text = currentNode?.delta?.toPlainText();
|
||||
if (currentNode != null &&
|
||||
currentNode.type == ParagraphBlockKeys.type &&
|
||||
text != null &&
|
||||
text.isEmpty) {
|
||||
final transaction = editorState.transaction;
|
||||
transaction.insertNode(
|
||||
selection.end.path.next,
|
||||
node,
|
||||
);
|
||||
transaction.deleteNode(currentNode);
|
||||
transaction.afterSelection = Selection.collapsed(
|
||||
Position(path: selection.end.path),
|
||||
);
|
||||
transaction.selectionExtraInfo = {};
|
||||
await editorState.apply(transaction);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await editorState.insertBlockAfterCurrentSelection(selection, node);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -182,6 +211,32 @@ class _AddBlockMenu extends StatelessWidget {
|
||||
onTap: (_, __) => _insertBlock(toggleListBlockNode()),
|
||||
),
|
||||
|
||||
// toggle headings
|
||||
TypeOptionMenuItemValue(
|
||||
value: ToggleListBlockKeys.type,
|
||||
backgroundColor: colorMap[ToggleListBlockKeys.type]!,
|
||||
text: LocaleKeys.document_slashMenu_name_toggleHeading1.tr(),
|
||||
icon: FlowySvgs.toggle_heading1_s,
|
||||
iconPadding: const EdgeInsets.all(3),
|
||||
onTap: (_, __) => _insertBlock(toggleHeadingNode()),
|
||||
),
|
||||
TypeOptionMenuItemValue(
|
||||
value: ToggleListBlockKeys.type,
|
||||
backgroundColor: colorMap[ToggleListBlockKeys.type]!,
|
||||
text: LocaleKeys.document_slashMenu_name_toggleHeading2.tr(),
|
||||
icon: FlowySvgs.toggle_heading2_s,
|
||||
iconPadding: const EdgeInsets.all(3),
|
||||
onTap: (_, __) => _insertBlock(toggleHeadingNode(level: 2)),
|
||||
),
|
||||
TypeOptionMenuItemValue(
|
||||
value: ToggleListBlockKeys.type,
|
||||
backgroundColor: colorMap[ToggleListBlockKeys.type]!,
|
||||
text: LocaleKeys.document_slashMenu_name_toggleHeading3.tr(),
|
||||
icon: FlowySvgs.toggle_heading3_s,
|
||||
iconPadding: const EdgeInsets.all(3),
|
||||
onTap: (_, __) => _insertBlock(toggleHeadingNode(level: 3)),
|
||||
),
|
||||
|
||||
// image
|
||||
TypeOptionMenuItemValue(
|
||||
value: ImageBlockKeys.type,
|
||||
|
@ -81,7 +81,7 @@ final toggleHeading1SlashMenuItem = SelectionMenuItem(
|
||||
getName: () => LocaleKeys.document_slashMenu_name_toggleHeading1.tr(),
|
||||
nameBuilder: _slashMenuItemNameBuilder,
|
||||
icon: (editorState, isSelected, style) => SelectableSvgWidget(
|
||||
data: FlowySvgs.slash_menu_icon_h1_s,
|
||||
data: FlowySvgs.toggle_heading1_s,
|
||||
isSelected: isSelected,
|
||||
style: style,
|
||||
),
|
||||
@ -99,7 +99,7 @@ final toggleHeading2SlashMenuItem = SelectionMenuItem(
|
||||
getName: () => LocaleKeys.document_slashMenu_name_toggleHeading2.tr(),
|
||||
nameBuilder: _slashMenuItemNameBuilder,
|
||||
icon: (editorState, isSelected, style) => SelectableSvgWidget(
|
||||
data: FlowySvgs.slash_menu_icon_h2_s,
|
||||
data: FlowySvgs.toggle_heading2_s,
|
||||
isSelected: isSelected,
|
||||
style: style,
|
||||
),
|
||||
@ -117,7 +117,7 @@ final toggleHeading3SlashMenuItem = SelectionMenuItem(
|
||||
getName: () => LocaleKeys.document_slashMenu_name_toggleHeading3.tr(),
|
||||
nameBuilder: _slashMenuItemNameBuilder,
|
||||
icon: (editorState, isSelected, style) => SelectableSvgWidget(
|
||||
data: FlowySvgs.slash_menu_icon_h3_s,
|
||||
data: FlowySvgs.toggle_heading3_s,
|
||||
isSelected: isSelected,
|
||||
style: style,
|
||||
),
|
||||
|
@ -341,6 +341,7 @@ class _ToggleListBlockComponentWidgetState
|
||||
..updateNode(node, {
|
||||
ToggleListBlockKeys.collapsed: !collapsed,
|
||||
});
|
||||
transaction.afterSelection = editorState.selection;
|
||||
await editorState.apply(transaction);
|
||||
}
|
||||
}
|
||||
|
@ -328,6 +328,16 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
didReceiveSpaceUpdate: () async {
|
||||
final (spaces, _, _) = await _getSpaces();
|
||||
final currentSpace = await _getLastOpenedSpace(spaces);
|
||||
|
||||
for (var i = 0; i < spaces.length; i++) {
|
||||
Log.info(
|
||||
'did receive space update[$i]: ${spaces[i].name}(${spaces[i].id})',
|
||||
);
|
||||
}
|
||||
Log.info(
|
||||
'did receive space update, current space: ${currentSpace?.name}(${currentSpace?.id})',
|
||||
);
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
spaces: spaces,
|
||||
|
4
frontend/resources/flowy_icons/16x/toggle_heading1.svg
Normal file
4
frontend/resources/flowy_icons/16x/toggle_heading1.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.18516 8.3457L0.738083 10.3849C0.444986 10.6292 0 10.4208 0 10.0392L0 5.96077C0 5.57924 0.444985 5.37082 0.738082 5.61507L3.18516 7.6543C3.40105 7.83421 3.40105 8.16579 3.18516 8.3457Z" fill="#333333"/>
|
||||
<path d="M5.3999 4V7.99982M5.3999 7.99982V11.9996M5.3999 7.99982L10.7844 8.0002M10.7844 8.0002V4.00039M10.7844 8.0002V12M13.0923 7.39954L14.2461 6.5996V11.9994M14.2461 11.9994H13.0923M14.2461 11.9994H15.3999" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 592 B |
4
frontend/resources/flowy_icons/16x/toggle_heading2.svg
Normal file
4
frontend/resources/flowy_icons/16x/toggle_heading2.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.18516 8.3457L0.738083 10.3849C0.444986 10.6292 0 10.4208 0 10.0392L0 5.96077C0 5.57924 0.444985 5.37082 0.738082 5.61507L3.18516 7.6543C3.40105 7.83421 3.40105 8.16579 3.18516 8.3457Z" fill="#333333"/>
|
||||
<path d="M15.3999 11.8468H12.7086V11.0218C12.7086 10.5848 12.9554 10.1855 13.3462 9.99008L14.8275 9.24942C15.1615 9.08241 15.3999 8.75986 15.3999 8.38645C15.3999 8.13105 15.3791 7.88048 15.3393 7.63636C15.2702 7.21369 14.9114 6.90752 14.4845 6.87333C14.2792 6.85688 14.0716 6.84849 13.862 6.84849C13.47 6.84849 13.0848 6.87783 12.7086 6.93442M5.3999 4.15332V7.99849M5.3999 7.99849V11.8436M5.3999 7.99849L10.7827 7.99884M10.7827 7.99884V4.15369M10.7827 7.99884V11.844" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 851 B |
4
frontend/resources/flowy_icons/16x/toggle_heading3.svg
Normal file
4
frontend/resources/flowy_icons/16x/toggle_heading3.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.18516 8.3457L0.738083 10.3849C0.444986 10.6292 0 10.4208 0 10.0392L0 5.96077C0 5.57924 0.444985 5.37082 0.738082 5.61507L3.18516 7.6543C3.40105 7.83421 3.40105 8.16579 3.18516 8.3457Z" fill="#333333"/>
|
||||
<path d="M14.967 9.34754C15.2394 9.72633 15.3999 10.191 15.3999 10.6932C15.3999 10.8654 15.381 11.0332 15.3453 11.1945C15.2663 11.551 14.9378 11.7807 14.5742 11.8142C14.3397 11.8357 14.1021 11.8467 13.862 11.8467C13.47 11.8467 13.0848 11.8173 12.7086 11.7607M14.967 9.34754C15.2394 8.96881 15.3999 8.50407 15.3999 8.0019C15.3999 7.8297 15.381 7.66197 15.3453 7.5006C15.2663 7.14406 14.9378 6.91438 14.5742 6.88098C14.3397 6.85945 14.1021 6.84844 13.862 6.84844C13.47 6.84844 13.0848 6.87777 12.7086 6.93437M14.967 9.34754H13.4775M5.3999 4.15332V7.99841M5.3999 7.99841V11.8435M5.3999 7.99841L10.7826 7.99877M10.7826 7.99877V4.15369M10.7826 7.99877V11.8438" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
Loading…
x
Reference in New Issue
Block a user