mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: implement draggable folder (#3083)
This commit is contained in:
parent
eb77346e5a
commit
266209caeb
@ -14,8 +14,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Calendar);
|
||||||
await tester.tapCreateCalendarButton();
|
|
||||||
|
|
||||||
// open setting
|
// open setting
|
||||||
await tester.tapDatabaseSettingButton();
|
await tester.tapDatabaseSettingButton();
|
||||||
@ -36,7 +35,11 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// Create calendar view
|
// Create calendar view
|
||||||
await tester.createNewPageWithName(ViewLayoutPB.Calendar, 'calendar');
|
const name = 'calendar';
|
||||||
|
await tester.createNewPageWithName(
|
||||||
|
name: name,
|
||||||
|
layout: ViewLayoutPB.Calendar,
|
||||||
|
);
|
||||||
|
|
||||||
// Open setting
|
// Open setting
|
||||||
await tester.tapDatabaseSettingButton();
|
await tester.tapDatabaseSettingButton();
|
||||||
@ -47,9 +50,9 @@ void main() {
|
|||||||
await tester.tapFirstDayOfWeekStartFromMonday();
|
await tester.tapFirstDayOfWeekStartFromMonday();
|
||||||
|
|
||||||
// Open the other page and open the new calendar page again
|
// Open the other page and open the new calendar page again
|
||||||
await tester.openPage(readme);
|
await tester.openPage(gettingStated);
|
||||||
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
||||||
await tester.openPage('calendar');
|
await tester.openPage(name, layout: ViewLayoutPB.Calendar);
|
||||||
|
|
||||||
// Open setting again and check the start from Monday is selected
|
// Open setting again and check the start from Monday is selected
|
||||||
await tester.tapDatabaseSettingButton();
|
await tester.tapDatabaseSettingButton();
|
||||||
@ -65,8 +68,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// Create the calendar view
|
// Create the calendar view
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Calendar);
|
||||||
await tester.tapCreateCalendarButton();
|
|
||||||
|
|
||||||
// Scroll until today's date cell is visible
|
// Scroll until today's date cell is visible
|
||||||
await tester.scrollToToday();
|
await tester.scrollToToday();
|
||||||
@ -135,8 +137,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// Create the calendar view
|
// Create the calendar view
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Calendar);
|
||||||
await tester.tapCreateCalendarButton();
|
|
||||||
|
|
||||||
// Create a new event on the first of this month
|
// Create a new event on the first of this month
|
||||||
final today = DateTime.now();
|
final today = DateTime.now();
|
||||||
|
@ -15,8 +15,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
await tester.editCell(
|
await tester.editCell(
|
||||||
rowIndex: 0,
|
rowIndex: 0,
|
||||||
@ -38,7 +37,10 @@ void main() {
|
|||||||
testWidgets('edit multiple text cells', (tester) async {
|
testWidgets('edit multiple text cells', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
await tester.createNewPageWithName(ViewLayoutPB.Grid, 'my grid');
|
await tester.createNewPageWithName(
|
||||||
|
name: 'my grid',
|
||||||
|
layout: ViewLayoutPB.Grid,
|
||||||
|
);
|
||||||
await tester.createField(FieldType.RichText, 'description');
|
await tester.createField(FieldType.RichText, 'description');
|
||||||
|
|
||||||
await tester.editCell(
|
await tester.editCell(
|
||||||
@ -75,8 +77,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
const fieldType = FieldType.Number;
|
const fieldType = FieldType.Number;
|
||||||
|
|
||||||
@ -134,8 +135,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
await tester.assertCheckboxCell(rowIndex: 0, isSelected: false);
|
await tester.assertCheckboxCell(rowIndex: 0, isSelected: false);
|
||||||
await tester.tapCheckboxCellInGrid(rowIndex: 0);
|
await tester.tapCheckboxCellInGrid(rowIndex: 0);
|
||||||
@ -153,8 +153,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
const fieldType = FieldType.CreatedTime;
|
const fieldType = FieldType.CreatedTime;
|
||||||
// Create a create time field
|
// Create a create time field
|
||||||
@ -172,8 +171,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
const fieldType = FieldType.LastEditedTime;
|
const fieldType = FieldType.LastEditedTime;
|
||||||
// Create a last time field
|
// Create a last time field
|
||||||
@ -191,8 +189,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
const fieldType = FieldType.DateTime;
|
const fieldType = FieldType.DateTime;
|
||||||
await tester.createField(fieldType, fieldType.name);
|
await tester.createField(fieldType, fieldType.name);
|
||||||
@ -288,9 +285,9 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
const fieldType = FieldType.SingleSelect;
|
const fieldType = FieldType.SingleSelect;
|
||||||
await tester.tapAddButton();
|
|
||||||
// When create a grid, it will create a single select field by default
|
// When create a grid, it will create a single select field by default
|
||||||
await tester.tapCreateGridButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
|
|
||||||
// Tap the cell to invoke the selection option editor
|
// Tap the cell to invoke the selection option editor
|
||||||
await tester.tapSelectOptionCellInGrid(rowIndex: 0, fieldType: fieldType);
|
await tester.tapSelectOptionCellInGrid(rowIndex: 0, fieldType: fieldType);
|
||||||
@ -366,8 +363,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
const fieldType = FieldType.MultiSelect;
|
const fieldType = FieldType.MultiSelect;
|
||||||
await tester.createField(fieldType, fieldType.name);
|
await tester.createField(fieldType, fieldType.name);
|
||||||
|
@ -17,8 +17,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// Invoke the field editor
|
// Invoke the field editor
|
||||||
await tester.tapGridFieldWithName('Name');
|
await tester.tapGridFieldWithName('Name');
|
||||||
@ -35,8 +34,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// Invoke the field editor
|
// Invoke the field editor
|
||||||
await tester.tapGridFieldWithName('Type');
|
await tester.tapGridFieldWithName('Type');
|
||||||
@ -58,8 +56,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// create a new grid
|
// create a new grid
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// create a field
|
// create a field
|
||||||
await tester.createField(FieldType.Checklist, 'checklist');
|
await tester.createField(FieldType.Checklist, 'checklist');
|
||||||
@ -73,8 +70,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// create a field
|
// create a field
|
||||||
await tester.createField(FieldType.Checkbox, 'New field 1');
|
await tester.createField(FieldType.Checkbox, 'New field 1');
|
||||||
@ -94,8 +90,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// create a field
|
// create a field
|
||||||
await tester.scrollToRight(find.byType(GridPage));
|
await tester.scrollToRight(find.byType(GridPage));
|
||||||
@ -115,8 +110,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// create a field
|
// create a field
|
||||||
await tester.scrollToRight(find.byType(GridPage));
|
await tester.scrollToRight(find.byType(GridPage));
|
||||||
@ -136,8 +130,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
await tester.scrollToRight(find.byType(GridPage));
|
await tester.scrollToRight(find.byType(GridPage));
|
||||||
await tester.tapNewPropertyButton();
|
await tester.tapNewPropertyButton();
|
||||||
@ -157,8 +150,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
for (final fieldType in [
|
for (final fieldType in [
|
||||||
FieldType.Checklist,
|
FieldType.Checklist,
|
||||||
@ -190,7 +182,9 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.createNewPageWithName(ViewLayoutPB.Grid);
|
await tester.createNewPageWithName(
|
||||||
|
layout: ViewLayoutPB.Grid,
|
||||||
|
);
|
||||||
|
|
||||||
// Invoke the field editor
|
// Invoke the field editor
|
||||||
await tester.tapGridFieldWithName('Type');
|
await tester.tapGridFieldWithName('Type');
|
||||||
|
@ -9,7 +9,7 @@ import 'util/database_test_op.dart';
|
|||||||
void main() {
|
void main() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
group('grid', () {
|
group('database filter', () {
|
||||||
testWidgets('add text filter', (tester) async {
|
testWidgets('add text filter', (tester) async {
|
||||||
await tester.openV020database();
|
await tester.openV020database();
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -19,8 +20,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// Create a new grid
|
// Create a new grid
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// Hover first row and then open the row page
|
// Hover first row and then open the row page
|
||||||
await tester.openFirstRowDetailPage();
|
await tester.openFirstRowDetailPage();
|
||||||
@ -34,8 +34,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// Create a new grid
|
// Create a new grid
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// Hover first row and then open the row page
|
// Hover first row and then open the row page
|
||||||
await tester.openFirstRowDetailPage();
|
await tester.openFirstRowDetailPage();
|
||||||
@ -55,8 +54,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// Create a new grid
|
// Create a new grid
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// Hover first row and then open the row page
|
// Hover first row and then open the row page
|
||||||
await tester.openFirstRowDetailPage();
|
await tester.openFirstRowDetailPage();
|
||||||
@ -85,8 +83,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// Create a new grid
|
// Create a new grid
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// Hover first row and then open the row page
|
// Hover first row and then open the row page
|
||||||
await tester.openFirstRowDetailPage();
|
await tester.openFirstRowDetailPage();
|
||||||
@ -108,8 +105,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// Create a new grid
|
// Create a new grid
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// Hover first row and then open the row page
|
// Hover first row and then open the row page
|
||||||
await tester.openFirstRowDetailPage();
|
await tester.openFirstRowDetailPage();
|
||||||
@ -144,8 +140,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// Create a new grid
|
// Create a new grid
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// Hover first row and then open the row page
|
// Hover first row and then open the row page
|
||||||
await tester.openFirstRowDetailPage();
|
await tester.openFirstRowDetailPage();
|
||||||
@ -160,8 +155,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// Create a new grid
|
// Create a new grid
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// Hover first row and then open the row page
|
// Hover first row and then open the row page
|
||||||
await tester.openFirstRowDetailPage();
|
await tester.openFirstRowDetailPage();
|
||||||
@ -201,8 +195,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// Create a new grid
|
// Create a new grid
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// Hover first row and then open the row page
|
// Hover first row and then open the row page
|
||||||
await tester.openFirstRowDetailPage();
|
await tester.openFirstRowDetailPage();
|
||||||
@ -241,8 +234,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// Create a new grid
|
// Create a new grid
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// Hover first row and then open the row page
|
// Hover first row and then open the row page
|
||||||
await tester.openFirstRowDetailPage();
|
await tester.openFirstRowDetailPage();
|
||||||
@ -258,8 +250,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// Create a new grid
|
// Create a new grid
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// Hover first row and then open the row page
|
// Hover first row and then open the row page
|
||||||
await tester.openFirstRowDetailPage();
|
await tester.openFirstRowDetailPage();
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
@ -12,8 +13,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
await tester.tapCreateRowButtonInGrid();
|
await tester.tapCreateRowButtonInGrid();
|
||||||
|
|
||||||
// The initial number of rows is 3
|
// The initial number of rows is 3
|
||||||
@ -25,8 +25,8 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
await tester.hoverOnFirstRowOfGrid();
|
await tester.hoverOnFirstRowOfGrid();
|
||||||
|
|
||||||
await tester.tapCreateRowButtonInRowMenuOfGrid();
|
await tester.tapCreateRowButtonInRowMenuOfGrid();
|
||||||
@ -41,8 +41,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
await tester.hoverOnFirstRowOfGrid();
|
await tester.hoverOnFirstRowOfGrid();
|
||||||
|
|
||||||
// Open the row menu and then click the delete
|
// Open the row menu and then click the delete
|
||||||
@ -60,8 +59,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
await tester.assertRowCountInGridPage(3);
|
await tester.assertRowCountInGridPage(3);
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
@ -13,8 +14,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// open setting
|
// open setting
|
||||||
await tester.tapDatabaseSettingButton();
|
await tester.tapDatabaseSettingButton();
|
||||||
@ -31,8 +31,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// open setting
|
// open setting
|
||||||
await tester.tapDatabaseSettingButton();
|
await tester.tapDatabaseSettingButton();
|
||||||
|
@ -15,8 +15,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// Create board view
|
// Create board view
|
||||||
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
|
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
|
||||||
@ -37,8 +36,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// Create board view
|
// Create board view
|
||||||
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
|
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
|
||||||
@ -63,8 +61,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateGridButton();
|
|
||||||
|
|
||||||
// Create board view
|
// Create board view
|
||||||
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
|
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
|
||||||
|
@ -17,9 +17,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// create a new document
|
// create a new document
|
||||||
await tester.tapAddButton();
|
await tester.createNewPageWithName();
|
||||||
await tester.tapCreateDocumentButton();
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// expect to see a new document
|
// expect to see a new document
|
||||||
tester.expectToSeePageName(
|
tester.expectToSeePageName(
|
||||||
@ -35,19 +33,21 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// delete the readme page
|
// delete the readme page
|
||||||
await tester.hoverOnPageName(readme);
|
await tester.hoverOnPageName(
|
||||||
await tester.tapDeletePageButton();
|
gettingStated,
|
||||||
|
onHover: () async => await tester.tapDeletePageButton(),
|
||||||
|
);
|
||||||
|
|
||||||
// the banner should show up and the readme page should be gone
|
// the banner should show up and the readme page should be gone
|
||||||
tester.expectToSeeDocumentBanner();
|
tester.expectToSeeDocumentBanner();
|
||||||
tester.expectNotToSeePageName(readme);
|
tester.expectNotToSeePageName(gettingStated);
|
||||||
|
|
||||||
// restore the readme page
|
// restore the readme page
|
||||||
await tester.tapRestoreButton();
|
await tester.tapRestoreButton();
|
||||||
|
|
||||||
// the banner should be gone and the readme page should be back
|
// the banner should be gone and the readme page should be back
|
||||||
tester.expectNotToSeeDocumentBanner();
|
tester.expectNotToSeeDocumentBanner();
|
||||||
tester.expectToSeePageName(readme);
|
tester.expectToSeePageName(gettingStated);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('delete the readme page and delete it permanently',
|
testWidgets('delete the readme page and delete it permanently',
|
||||||
@ -57,19 +57,21 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// delete the readme page
|
// delete the readme page
|
||||||
await tester.hoverOnPageName(readme);
|
await tester.hoverOnPageName(
|
||||||
await tester.tapDeletePageButton();
|
gettingStated,
|
||||||
|
onHover: () async => await tester.tapDeletePageButton(),
|
||||||
|
);
|
||||||
|
|
||||||
// the banner should show up and the readme page should be gone
|
// the banner should show up and the readme page should be gone
|
||||||
tester.expectToSeeDocumentBanner();
|
tester.expectToSeeDocumentBanner();
|
||||||
tester.expectNotToSeePageName(readme);
|
tester.expectNotToSeePageName(gettingStated);
|
||||||
|
|
||||||
// delete the page permanently
|
// delete the page permanently
|
||||||
await tester.tapDeletePermanentlyButton();
|
await tester.tapDeletePermanentlyButton();
|
||||||
|
|
||||||
// the banner should be gone and the readme page should be gone
|
// the banner should be gone and the readme page should be gone
|
||||||
tester.expectNotToSeeDocumentBanner();
|
tester.expectNotToSeeDocumentBanner();
|
||||||
tester.expectNotToSeePageName(readme);
|
tester.expectNotToSeePageName(gettingStated);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -73,13 +73,13 @@ Future<void> insertReferenceDatabase(
|
|||||||
final id = uuid();
|
final id = uuid();
|
||||||
final name = '${layout.name}_$id';
|
final name = '${layout.name}_$id';
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
layout,
|
name: name,
|
||||||
name,
|
layout: layout,
|
||||||
);
|
);
|
||||||
// create a new document
|
// create a new document
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
ViewLayoutPB.Document,
|
name: 'insert_a_reference_${layout.name}',
|
||||||
'insert_a_reference_${layout.name}',
|
layout: ViewLayoutPB.Document,
|
||||||
);
|
);
|
||||||
// tap the first line of the document
|
// tap the first line of the document
|
||||||
await tester.editor.tapLineOfEditorAt(0);
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
|
@ -21,8 +21,8 @@ void main() {
|
|||||||
|
|
||||||
// create a new document
|
// create a new document
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
ViewLayoutPB.Document,
|
name: LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
layout: ViewLayoutPB.Document,
|
||||||
);
|
);
|
||||||
|
|
||||||
// tap the first line of the document
|
// tap the first line of the document
|
||||||
@ -67,8 +67,8 @@ void main() {
|
|||||||
|
|
||||||
// create a new document
|
// create a new document
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
ViewLayoutPB.Document,
|
name: LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
layout: ViewLayoutPB.Document,
|
||||||
);
|
);
|
||||||
|
|
||||||
// tap the first line of the document
|
// tap the first line of the document
|
||||||
|
@ -62,9 +62,11 @@ void main() {
|
|||||||
final pageName = await insertingInlinePage(tester, ViewLayoutPB.Document);
|
final pageName = await insertingInlinePage(tester, ViewLayoutPB.Document);
|
||||||
|
|
||||||
// rename
|
// rename
|
||||||
await tester.hoverOnPageName(pageName);
|
|
||||||
const newName = 'RenameToNewPageName';
|
const newName = 'RenameToNewPageName';
|
||||||
await tester.renamePage(newName);
|
await tester.hoverOnPageName(
|
||||||
|
pageName,
|
||||||
|
onHover: () async => await tester.renamePage(newName),
|
||||||
|
);
|
||||||
final finder = find.descendant(
|
final finder = find.descendant(
|
||||||
of: find.byType(MentionPageBlock),
|
of: find.byType(MentionPageBlock),
|
||||||
matching: find.findTextInFlowyText(newName),
|
matching: find.findTextInFlowyText(newName),
|
||||||
@ -79,8 +81,11 @@ void main() {
|
|||||||
final pageName = await insertingInlinePage(tester, ViewLayoutPB.Grid);
|
final pageName = await insertingInlinePage(tester, ViewLayoutPB.Grid);
|
||||||
|
|
||||||
// rename
|
// rename
|
||||||
await tester.hoverOnPageName(pageName);
|
await tester.hoverOnPageName(
|
||||||
await tester.tapDeletePageButton();
|
pageName,
|
||||||
|
layout: ViewLayoutPB.Grid,
|
||||||
|
onHover: () async => await tester.tapDeletePageButton(),
|
||||||
|
);
|
||||||
final finder = find.descendant(
|
final finder = find.descendant(
|
||||||
of: find.byType(MentionPageBlock),
|
of: find.byType(MentionPageBlock),
|
||||||
matching: find.findTextInFlowyText(pageName),
|
matching: find.findTextInFlowyText(pageName),
|
||||||
@ -101,13 +106,14 @@ Future<String> insertingInlinePage(
|
|||||||
final id = uuid();
|
final id = uuid();
|
||||||
final name = '${layout.name}_$id';
|
final name = '${layout.name}_$id';
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
layout,
|
name: name,
|
||||||
name,
|
layout: layout,
|
||||||
|
openAfterCreated: false,
|
||||||
);
|
);
|
||||||
// create a new document
|
// create a new document
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
ViewLayoutPB.Document,
|
name: 'insert_a_inline_page_${layout.name}',
|
||||||
'insert_a_inline_page_${layout.name}',
|
layout: ViewLayoutPB.Document,
|
||||||
);
|
);
|
||||||
// tap the first line of the document
|
// tap the first line of the document
|
||||||
await tester.editor.tapLineOfEditorAt(0);
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
|
@ -25,7 +25,7 @@ void main() {
|
|||||||
|
|
||||||
// create a new document
|
// create a new document
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
ViewLayoutPB.Document,
|
layout: ViewLayoutPB.Document,
|
||||||
);
|
);
|
||||||
|
|
||||||
// tap the first line of the document
|
// tap the first line of the document
|
||||||
|
@ -16,8 +16,8 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
ViewLayoutPB.Document,
|
name: 'outline_test',
|
||||||
'outline_test',
|
layout: ViewLayoutPB.Document,
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.editor.tapLineOfEditorAt(0);
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
@ -33,8 +33,8 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
ViewLayoutPB.Document,
|
name: 'outline_test',
|
||||||
'outline_test',
|
layout: ViewLayoutPB.Document,
|
||||||
);
|
);
|
||||||
await tester.editor.tapLineOfEditorAt(0);
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ void main() {
|
|||||||
|
|
||||||
// create a new document
|
// create a new document
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
ViewLayoutPB.Document,
|
layout: ViewLayoutPB.Document,
|
||||||
);
|
);
|
||||||
|
|
||||||
// tap the first line of the document
|
// tap the first line of the document
|
||||||
@ -78,7 +78,7 @@ void main() {
|
|||||||
|
|
||||||
// create a new document
|
// create a new document
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
ViewLayoutPB.Document,
|
layout: ViewLayoutPB.Document,
|
||||||
);
|
);
|
||||||
|
|
||||||
// tap the first line of the document
|
// tap the first line of the document
|
||||||
@ -118,7 +118,7 @@ void main() {
|
|||||||
|
|
||||||
// create a new document
|
// create a new document
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
ViewLayoutPB.Document,
|
layout: ViewLayoutPB.Document,
|
||||||
);
|
);
|
||||||
|
|
||||||
// tap the first line of the document
|
// tap the first line of the document
|
||||||
@ -156,7 +156,7 @@ void main() {
|
|||||||
|
|
||||||
// create a new document
|
// create a new document
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
ViewLayoutPB.Document,
|
layout: ViewLayoutPB.Document,
|
||||||
);
|
);
|
||||||
|
|
||||||
// tap the first line of the document
|
// tap the first line of the document
|
||||||
@ -191,7 +191,7 @@ void main() {
|
|||||||
|
|
||||||
// create a new document
|
// create a new document
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
ViewLayoutPB.Document,
|
layout: ViewLayoutPB.Document,
|
||||||
);
|
);
|
||||||
|
|
||||||
// tap the first line of the document
|
// tap the first line of the document
|
||||||
|
@ -18,7 +18,10 @@ void main() {
|
|||||||
|
|
||||||
// create a new document called Sample
|
// create a new document called Sample
|
||||||
const pageName = 'Sample';
|
const pageName = 'Sample';
|
||||||
await tester.createNewPageWithName(ViewLayoutPB.Document, pageName);
|
await tester.createNewPageWithName(
|
||||||
|
name: pageName,
|
||||||
|
layout: ViewLayoutPB.Document,
|
||||||
|
);
|
||||||
|
|
||||||
// focus on the editor
|
// focus on the editor
|
||||||
await tester.editor.tapLineOfEditorAt(0);
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
@ -56,7 +59,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// switch to other page and switch back
|
// switch to other page and switch back
|
||||||
await tester.openPage(readme);
|
await tester.openPage(gettingStated);
|
||||||
await tester.openPage(pageName);
|
await tester.openPage(pageName);
|
||||||
|
|
||||||
// the numbered list should be kept
|
// the numbered list should be kept
|
||||||
@ -72,7 +75,10 @@ void main() {
|
|||||||
|
|
||||||
// create a new document called Sample
|
// create a new document called Sample
|
||||||
const pageName = 'Sample';
|
const pageName = 'Sample';
|
||||||
await tester.createNewPageWithName(ViewLayoutPB.Document, pageName);
|
await tester.createNewPageWithName(
|
||||||
|
name: pageName,
|
||||||
|
layout: ViewLayoutPB.Document,
|
||||||
|
);
|
||||||
|
|
||||||
// focus on the editor
|
// focus on the editor
|
||||||
await tester.editor.tapLineOfEditorAt(0);
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
@ -85,7 +91,7 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// switch to other page and switch back
|
// switch to other page and switch back
|
||||||
await tester.openPage(readme);
|
await tester.openPage(gettingStated);
|
||||||
await tester.openPage(pageName);
|
await tester.openPage(pageName);
|
||||||
|
|
||||||
// this screenshots are different on different platform, so comment it out temporarily.
|
// this screenshots are different on different platform, so comment it out temporarily.
|
||||||
|
@ -16,10 +16,10 @@ void main() {
|
|||||||
final context = await tester.initializeAppFlowy();
|
final context = await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// expect to see a readme page
|
// expect to see a getting started page
|
||||||
tester.expectToSeePageName(readme);
|
tester.expectToSeePageName(gettingStated);
|
||||||
|
|
||||||
await tester.tapAddButton();
|
await tester.tapAddViewButton();
|
||||||
await tester.tapImportButton();
|
await tester.tapImportButton();
|
||||||
|
|
||||||
final testFileNames = ['test1.md', 'test2.md'];
|
final testFileNames = ['test1.md', 'test2.md'];
|
||||||
|
@ -14,6 +14,7 @@ import 'document/document_test_runner.dart' as document_test_runner;
|
|||||||
import 'import_files_test.dart' as import_files_test;
|
import 'import_files_test.dart' as import_files_test;
|
||||||
import 'share_markdown_test.dart' as share_markdown_test;
|
import 'share_markdown_test.dart' as share_markdown_test;
|
||||||
import 'switch_folder_test.dart' as switch_folder_test;
|
import 'switch_folder_test.dart' as switch_folder_test;
|
||||||
|
import 'sidebar/sidebar_test_runner.dart' as sidebar_test_runner;
|
||||||
|
|
||||||
/// The main task runner for all integration tests in AppFlowy.
|
/// The main task runner for all integration tests in AppFlowy.
|
||||||
///
|
///
|
||||||
@ -31,6 +32,9 @@ void main() {
|
|||||||
// Document integration tests
|
// Document integration tests
|
||||||
document_test_runner.startTesting();
|
document_test_runner.startTesting();
|
||||||
|
|
||||||
|
// Sidebar integration tests
|
||||||
|
sidebar_test_runner.startTesting();
|
||||||
|
|
||||||
// Database integration tests
|
// Database integration tests
|
||||||
database_cell_test.main();
|
database_cell_test.main();
|
||||||
database_field_test.main();
|
database_field_test.main();
|
||||||
|
@ -16,7 +16,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// expect to see a readme page
|
// expect to see a readme page
|
||||||
tester.expectToSeePageName(readme);
|
tester.expectToSeePageName(gettingStated);
|
||||||
|
|
||||||
// mock the file picker
|
// mock the file picker
|
||||||
final path = await mockSaveFilePath(
|
final path = await mockSaveFilePath(
|
||||||
@ -42,12 +42,16 @@ void main() {
|
|||||||
final context = await tester.initializeAppFlowy();
|
final context = await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// expect to see a readme page
|
// expect to see a getting started page
|
||||||
tester.expectToSeePageName(readme);
|
tester.expectToSeePageName(gettingStated);
|
||||||
|
|
||||||
// rename the document
|
// rename the document
|
||||||
await tester.hoverOnPageName(readme);
|
await tester.hoverOnPageName(
|
||||||
await tester.renamePage('example');
|
gettingStated,
|
||||||
|
onHover: () async {
|
||||||
|
await tester.renamePage('example');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
final shareButton = find.byType(ShareActionList);
|
final shareButton = find.byType(ShareActionList);
|
||||||
final shareButtonState =
|
final shareButtonState =
|
||||||
|
@ -0,0 +1,142 @@
|
|||||||
|
import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import '../util/util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('sidebar test', () {
|
||||||
|
testWidgets('create a new page', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
// create a new page
|
||||||
|
const name = 'Hello AppFlowy';
|
||||||
|
await tester.tapNewPageButton();
|
||||||
|
await tester.enterText(find.byType(TextFormField), name);
|
||||||
|
await tester.tapOKButton();
|
||||||
|
|
||||||
|
// expect to see a new document
|
||||||
|
tester.expectToSeePageName(
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
// and with one paragraph block
|
||||||
|
expect(find.byType(TextBlockComponentWidget), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('create a new document, grid, board and calendar',
|
||||||
|
(tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
for (final layout in ViewLayoutPB.values) {
|
||||||
|
// create a new page
|
||||||
|
final name = 'AppFlowy_$layout';
|
||||||
|
await tester.createNewPageWithName(
|
||||||
|
name: name,
|
||||||
|
layout: layout,
|
||||||
|
);
|
||||||
|
|
||||||
|
// expect to see a new page
|
||||||
|
tester.expectToSeePageName(
|
||||||
|
name,
|
||||||
|
layout: layout,
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (layout) {
|
||||||
|
case ViewLayoutPB.Document:
|
||||||
|
// and with one paragraph block
|
||||||
|
expect(find.byType(TextBlockComponentWidget), findsOneWidget);
|
||||||
|
break;
|
||||||
|
case ViewLayoutPB.Grid:
|
||||||
|
expect(find.byType(GridPage), findsOneWidget);
|
||||||
|
break;
|
||||||
|
case ViewLayoutPB.Board:
|
||||||
|
expect(find.byType(BoardPage), findsOneWidget);
|
||||||
|
break;
|
||||||
|
case ViewLayoutPB.Calendar:
|
||||||
|
expect(find.byType(CalendarPage), findsOneWidget);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.openPage(gettingStated);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('create some nested pages, and move them', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
final names = [1, 2, 3, 4].map((e) => 'document_$e').toList();
|
||||||
|
for (var i = 0; i < names.length; i++) {
|
||||||
|
final parentName = i == 0 ? gettingStated : names[i - 1];
|
||||||
|
await tester.createNewPageWithName(
|
||||||
|
name: names[i],
|
||||||
|
parentName: parentName,
|
||||||
|
layout: ViewLayoutPB.Document,
|
||||||
|
);
|
||||||
|
tester.expectToSeePageName(names[i], parentName: parentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// move the document_3 to the getting started page
|
||||||
|
await tester.movePageToOtherPage(
|
||||||
|
name: names[3],
|
||||||
|
parentName: gettingStated,
|
||||||
|
layout: ViewLayoutPB.Document,
|
||||||
|
parentLayout: ViewLayoutPB.Document,
|
||||||
|
);
|
||||||
|
final fromId = tester
|
||||||
|
.widget<SingleInnerViewItem>(tester.findPageName(names[3]))
|
||||||
|
.view
|
||||||
|
.parentViewId;
|
||||||
|
final toId = tester
|
||||||
|
.widget<SingleInnerViewItem>(tester.findPageName(gettingStated))
|
||||||
|
.view
|
||||||
|
.id;
|
||||||
|
expect(fromId, toId);
|
||||||
|
|
||||||
|
// move the document_2 before document_1
|
||||||
|
await tester.movePageToOtherPage(
|
||||||
|
name: names[2],
|
||||||
|
parentName: gettingStated,
|
||||||
|
layout: ViewLayoutPB.Document,
|
||||||
|
parentLayout: ViewLayoutPB.Document,
|
||||||
|
position: DraggableHoverPosition.bottom,
|
||||||
|
);
|
||||||
|
final childViews = tester
|
||||||
|
.widget<SingleInnerViewItem>(tester.findPageName(gettingStated))
|
||||||
|
.view
|
||||||
|
.childViews;
|
||||||
|
expect(
|
||||||
|
childViews[0].id,
|
||||||
|
tester
|
||||||
|
.widget<SingleInnerViewItem>(tester.findPageName(names[2]))
|
||||||
|
.view
|
||||||
|
.id,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
childViews[1].id,
|
||||||
|
tester
|
||||||
|
.widget<SingleInnerViewItem>(tester.findPageName(names[0]))
|
||||||
|
.view
|
||||||
|
.id,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
childViews[2].id,
|
||||||
|
tester
|
||||||
|
.widget<SingleInnerViewItem>(tester.findPageName(names[3]))
|
||||||
|
.view
|
||||||
|
.id,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import 'sidebar_test.dart' as sidebar_test;
|
||||||
|
|
||||||
|
void startTesting() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// Sidebar integration tests
|
||||||
|
sidebar_test.main();
|
||||||
|
}
|
@ -29,8 +29,14 @@ void main() {
|
|||||||
findsNothing,
|
findsNothing,
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.createNewPageWithName(ViewLayoutPB.Calendar, _calendarName);
|
await tester.createNewPageWithName(
|
||||||
await tester.createNewPageWithName(ViewLayoutPB.Document, _documentName);
|
name: _calendarName,
|
||||||
|
layout: ViewLayoutPB.Calendar,
|
||||||
|
);
|
||||||
|
await tester.createNewPageWithName(
|
||||||
|
name: _documentName,
|
||||||
|
layout: ViewLayoutPB.Document,
|
||||||
|
);
|
||||||
|
|
||||||
// Navigate current view to "Read me" document again
|
// Navigate current view to "Read me" document again
|
||||||
await tester.tapButtonWithName(_readmeName);
|
await tester.tapButtonWithName(_readmeName);
|
||||||
|
@ -91,7 +91,6 @@ extension AppFlowyTestBase on WidgetTester {
|
|||||||
warnIfMissed: warnIfMissed,
|
warnIfMissed: warnIfMissed,
|
||||||
);
|
);
|
||||||
await pumpAndSettle(Duration(milliseconds: milliseconds));
|
await pumpAndSettle(Duration(milliseconds: milliseconds));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> tapButtonWithName(
|
Future<void> tapButtonWithName(
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
|
||||||
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
|
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
|
||||||
import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
|
import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/app/header/add_button.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
@ -24,35 +28,48 @@ extension CommonOperations on WidgetTester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Tap the + button on the home page.
|
/// Tap the + button on the home page.
|
||||||
Future<void> tapAddButton() async {
|
Future<void> tapAddViewButton({
|
||||||
final addButton = find.byType(AddButton);
|
String name = gettingStated,
|
||||||
await tapButton(addButton);
|
}) async {
|
||||||
|
await hoverOnPageName(
|
||||||
|
name,
|
||||||
|
onHover: () async {
|
||||||
|
final addButton = find.byType(ViewAddButton);
|
||||||
|
await tapButton(addButton);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tap the 'New Page' Button on the sidebar.
|
||||||
|
Future<void> tapNewPageButton() async {
|
||||||
|
final newPageButton = find.byType(SidebarNewPageButton);
|
||||||
|
await tapButton(newPageButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tap the create document button.
|
/// Tap the create document button.
|
||||||
///
|
///
|
||||||
/// Must call [tapAddButton] first.
|
/// Must call [tapAddViewButton] first.
|
||||||
Future<void> tapCreateDocumentButton() async {
|
Future<void> tapCreateDocumentButton() async {
|
||||||
await tapButtonWithName(LocaleKeys.document_menuName.tr());
|
await tapButtonWithName(LocaleKeys.document_menuName.tr());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tap the create grid button.
|
/// Tap the create grid button.
|
||||||
///
|
///
|
||||||
/// Must call [tapAddButton] first.
|
/// Must call [tapAddViewButton] first.
|
||||||
Future<void> tapCreateGridButton() async {
|
Future<void> tapCreateGridButton() async {
|
||||||
await tapButtonWithName(LocaleKeys.grid_menuName.tr());
|
await tapButtonWithName(LocaleKeys.grid_menuName.tr());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tap the create grid button.
|
/// Tap the create grid button.
|
||||||
///
|
///
|
||||||
/// Must call [tapAddButton] first.
|
/// Must call [tapAddViewButton] first.
|
||||||
Future<void> tapCreateCalendarButton() async {
|
Future<void> tapCreateCalendarButton() async {
|
||||||
await tapButtonWithName(LocaleKeys.calendar_menuName.tr());
|
await tapButtonWithName(LocaleKeys.calendar_menuName.tr());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tap the import button.
|
/// Tap the import button.
|
||||||
///
|
///
|
||||||
/// Must call [tapAddButton] first.
|
/// Must call [tapAddViewButton] first.
|
||||||
Future<void> tapImportButton() async {
|
Future<void> tapImportButton() async {
|
||||||
await tapButtonWithName(LocaleKeys.moreAction_import.tr());
|
await tapButtonWithName(LocaleKeys.moreAction_import.tr());
|
||||||
}
|
}
|
||||||
@ -116,6 +133,7 @@ extension CommonOperations on WidgetTester {
|
|||||||
Finder finder, {
|
Finder finder, {
|
||||||
Offset? offset,
|
Offset? offset,
|
||||||
Future<void> Function()? onHover,
|
Future<void> Function()? onHover,
|
||||||
|
bool removePointer = true,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final gesture = await createGesture(kind: PointerDeviceKind.mouse);
|
final gesture = await createGesture(kind: PointerDeviceKind.mouse);
|
||||||
@ -133,19 +151,30 @@ extension CommonOperations on WidgetTester {
|
|||||||
/// Hover on the page name.
|
/// Hover on the page name.
|
||||||
Future<void> hoverOnPageName(
|
Future<void> hoverOnPageName(
|
||||||
String name, {
|
String name, {
|
||||||
|
ViewLayoutPB layout = ViewLayoutPB.Document,
|
||||||
Future<void> Function()? onHover,
|
Future<void> Function()? onHover,
|
||||||
bool useLast = true,
|
bool useLast = true,
|
||||||
}) async {
|
}) async {
|
||||||
|
final pageNames = findPageName(name, layout: layout);
|
||||||
if (useLast) {
|
if (useLast) {
|
||||||
await hoverOnWidget(findPageName(name).last, onHover: onHover);
|
await hoverOnWidget(
|
||||||
|
pageNames.last,
|
||||||
|
onHover: onHover,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await hoverOnWidget(findPageName(name).first, onHover: onHover);
|
await hoverOnWidget(
|
||||||
|
pageNames.first,
|
||||||
|
onHover: onHover,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// open the page with given name.
|
/// open the page with given name.
|
||||||
Future<void> openPage(String name) async {
|
Future<void> openPage(
|
||||||
final finder = findPageName(name);
|
String name, {
|
||||||
|
ViewLayoutPB layout = ViewLayoutPB.Document,
|
||||||
|
}) async {
|
||||||
|
final finder = findPageName(name, layout: layout);
|
||||||
expect(finder, findsOneWidget);
|
expect(finder, findsOneWidget);
|
||||||
await tapButton(finder);
|
await tapButton(finder);
|
||||||
}
|
}
|
||||||
@ -154,20 +183,20 @@ extension CommonOperations on WidgetTester {
|
|||||||
///
|
///
|
||||||
/// Must call [hoverOnPageName] first.
|
/// Must call [hoverOnPageName] first.
|
||||||
Future<void> tapPageOptionButton() async {
|
Future<void> tapPageOptionButton() async {
|
||||||
final optionButton = find.byType(ViewDisclosureButton);
|
final optionButton = find.byType(ViewMoreActionButton);
|
||||||
await tapButton(optionButton);
|
await tapButton(optionButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tap the delete page button.
|
/// Tap the delete page button.
|
||||||
Future<void> tapDeletePageButton() async {
|
Future<void> tapDeletePageButton() async {
|
||||||
await tapPageOptionButton();
|
await tapPageOptionButton();
|
||||||
await tapButtonWithName(ViewDisclosureAction.delete.name);
|
await tapButtonWithName(ViewMoreActionType.delete.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tap the rename page button.
|
/// Tap the rename page button.
|
||||||
Future<void> tapRenamePageButton() async {
|
Future<void> tapRenamePageButton() async {
|
||||||
await tapPageOptionButton();
|
await tapPageOptionButton();
|
||||||
await tapButtonWithName(ViewDisclosureAction.rename.name);
|
await tapButtonWithName(ViewMoreActionType.rename.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rename the page.
|
/// Rename the page.
|
||||||
@ -224,12 +253,14 @@ extension CommonOperations on WidgetTester {
|
|||||||
await tapButton(markdownButton);
|
await tapButton(markdownButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> createNewPageWithName(
|
Future<void> createNewPageWithName({
|
||||||
ViewLayoutPB layout, [
|
|
||||||
String? name,
|
String? name,
|
||||||
]) async {
|
ViewLayoutPB layout = ViewLayoutPB.Document,
|
||||||
|
String? parentName,
|
||||||
|
bool openAfterCreated = true,
|
||||||
|
}) async {
|
||||||
// create a new page
|
// create a new page
|
||||||
await tapAddButton();
|
await tapAddViewButton(name: parentName ?? gettingStated);
|
||||||
await tapButtonWithName(layout.menuName);
|
await tapButtonWithName(layout.menuName);
|
||||||
await pumpAndSettle();
|
await pumpAndSettle();
|
||||||
|
|
||||||
@ -237,6 +268,7 @@ extension CommonOperations on WidgetTester {
|
|||||||
if (name != null) {
|
if (name != null) {
|
||||||
await hoverOnPageName(
|
await hoverOnPageName(
|
||||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||||
|
layout: layout,
|
||||||
onHover: () async {
|
onHover: () async {
|
||||||
await renamePage(name);
|
await renamePage(name);
|
||||||
await pumpAndSettle();
|
await pumpAndSettle();
|
||||||
@ -244,6 +276,16 @@ extension CommonOperations on WidgetTester {
|
|||||||
);
|
);
|
||||||
await pumpAndSettle();
|
await pumpAndSettle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// open the page after created
|
||||||
|
if (openAfterCreated) {
|
||||||
|
await openPage(
|
||||||
|
// if the name is null, use the default name
|
||||||
|
name ?? LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||||
|
layout: layout,
|
||||||
|
);
|
||||||
|
await pumpAndSettle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> simulateKeyEvent(
|
Future<void> simulateKeyEvent(
|
||||||
@ -289,6 +331,34 @@ extension CommonOperations on WidgetTester {
|
|||||||
await tap(find.text(LocaleKeys.disclosureAction_openNewTab.tr()));
|
await tap(find.text(LocaleKeys.disclosureAction_openNewTab.tr()));
|
||||||
await pumpAndSettle();
|
await pumpAndSettle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> movePageToOtherPage({
|
||||||
|
required String name,
|
||||||
|
required String parentName,
|
||||||
|
required ViewLayoutPB layout,
|
||||||
|
required ViewLayoutPB parentLayout,
|
||||||
|
DraggableHoverPosition position = DraggableHoverPosition.center,
|
||||||
|
}) async {
|
||||||
|
final from = findPageName(name, layout: layout);
|
||||||
|
final to = findPageName(parentName, layout: parentLayout);
|
||||||
|
final gesture = await startGesture(getCenter(from));
|
||||||
|
Offset offset = Offset.zero;
|
||||||
|
switch (position) {
|
||||||
|
case DraggableHoverPosition.center:
|
||||||
|
offset = getCenter(to);
|
||||||
|
break;
|
||||||
|
case DraggableHoverPosition.top:
|
||||||
|
offset = getTopLeft(to);
|
||||||
|
break;
|
||||||
|
case DraggableHoverPosition.bottom:
|
||||||
|
offset = getBottomLeft(to);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
await gesture.moveTo(offset);
|
||||||
|
await gesture.up();
|
||||||
|
await pumpAndSettle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ViewLayoutPBTest on ViewLayoutPB {
|
extension ViewLayoutPBTest on ViewLayoutPB {
|
||||||
|
@ -76,9 +76,9 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
await tapGoButton();
|
await tapGoButton();
|
||||||
|
|
||||||
// expect to see a readme page
|
// expect to see a readme page
|
||||||
expectToSeePageName(readme);
|
expectToSeePageName(gettingStated);
|
||||||
|
|
||||||
await tapAddButton();
|
await tapAddViewButton();
|
||||||
await tapImportButton();
|
await tapImportButton();
|
||||||
|
|
||||||
final testFileNames = ['v020.afdb'];
|
final testFileNames = ['v020.afdb'];
|
||||||
@ -102,7 +102,8 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
paths: paths,
|
paths: paths,
|
||||||
);
|
);
|
||||||
await tapDatabaseRawDataButton();
|
await tapDatabaseRawDataButton();
|
||||||
await openPage('v020');
|
await pumpAndSettle();
|
||||||
|
await openPage('v020', layout: ViewLayoutPB.Grid);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> hoverOnFirstRowOfGrid() async {
|
Future<void> hoverOnFirstRowOfGrid() async {
|
||||||
|
@ -3,29 +3,51 @@ import 'package:appflowy/plugins/document/presentation/banner.dart';
|
|||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
|
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
const String readme = 'Read me';
|
// const String readme = 'Read me';
|
||||||
|
const String gettingStated = '⭐️ Getting started';
|
||||||
|
|
||||||
extension Expectation on WidgetTester {
|
extension Expectation on WidgetTester {
|
||||||
/// Expect to see the home page and with a default read me page.
|
/// Expect to see the home page and with a default read me page.
|
||||||
void expectToSeeHomePage() {
|
void expectToSeeHomePage() {
|
||||||
expect(find.byType(HomeStack), findsOneWidget);
|
expect(find.byType(HomeStack), findsOneWidget);
|
||||||
expect(find.textContaining(readme), findsWidgets);
|
expect(find.textContaining(gettingStated), findsWidgets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expect to see the page name on the home page.
|
/// Expect to see the page name on the home page.
|
||||||
void expectToSeePageName(String name) {
|
void expectToSeePageName(
|
||||||
final pageName = findPageName(name);
|
String name, {
|
||||||
|
String? parentName,
|
||||||
|
ViewLayoutPB layout = ViewLayoutPB.Document,
|
||||||
|
ViewLayoutPB parentLayout = ViewLayoutPB.Document,
|
||||||
|
}) {
|
||||||
|
final pageName = findPageName(
|
||||||
|
name,
|
||||||
|
layout: layout,
|
||||||
|
parentName: parentName,
|
||||||
|
parentLayout: parentLayout,
|
||||||
|
);
|
||||||
expect(pageName, findsOneWidget);
|
expect(pageName, findsOneWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expect not to see the page name on the home page.
|
/// Expect not to see the page name on the home page.
|
||||||
void expectNotToSeePageName(String name) {
|
void expectNotToSeePageName(
|
||||||
final pageName = findPageName(name);
|
String name, {
|
||||||
|
String? parentName,
|
||||||
|
ViewLayoutPB layout = ViewLayoutPB.Document,
|
||||||
|
ViewLayoutPB parentLayout = ViewLayoutPB.Document,
|
||||||
|
}) {
|
||||||
|
final pageName = findPageName(
|
||||||
|
name,
|
||||||
|
layout: layout,
|
||||||
|
parentName: parentName,
|
||||||
|
parentLayout: parentLayout,
|
||||||
|
);
|
||||||
expect(pageName, findsNothing);
|
expect(pageName, findsNothing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,10 +148,31 @@ extension Expectation on WidgetTester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Find the page name on the home page.
|
/// Find the page name on the home page.
|
||||||
Finder findPageName(String name) {
|
Finder findPageName(
|
||||||
return find.byWidgetPredicate(
|
String name, {
|
||||||
(widget) => widget is ViewSectionItem && widget.view.name == name,
|
ViewLayoutPB layout = ViewLayoutPB.Document,
|
||||||
skipOffstage: false,
|
String? parentName,
|
||||||
|
ViewLayoutPB parentLayout = ViewLayoutPB.Document,
|
||||||
|
}) {
|
||||||
|
if (parentName == null) {
|
||||||
|
return find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is SingleInnerViewItem &&
|
||||||
|
widget.view.name == name &&
|
||||||
|
widget.view.layout == layout,
|
||||||
|
skipOffstage: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return find.descendant(
|
||||||
|
of: find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is ViewItem &&
|
||||||
|
widget.view.name == name &&
|
||||||
|
widget.view.layout == layout,
|
||||||
|
skipOffstage: false,
|
||||||
|
),
|
||||||
|
matching: findPageName(name),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,4 +29,10 @@ class KVKeys {
|
|||||||
'kDocumentAppearanceFontSize';
|
'kDocumentAppearanceFontSize';
|
||||||
static const String kDocumentAppearanceFontFamily =
|
static const String kDocumentAppearanceFontFamily =
|
||||||
'kDocumentAppearanceFontFamily';
|
'kDocumentAppearanceFontFamily';
|
||||||
|
|
||||||
|
/// The key for saving the expanded views
|
||||||
|
///
|
||||||
|
/// The value is a json string with the following format:
|
||||||
|
/// {'viewId': true, 'viewId2': false}
|
||||||
|
static const String expandedViews = 'expandedViews';
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||||
import 'package:appflowy/plugins/trash/application/trash_service.dart';
|
import 'package:appflowy/plugins/trash/application/trash_service.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/view/prelude.dart';
|
import 'package:appflowy/workspace/application/view/prelude.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
|
||||||
@ -109,6 +110,11 @@ class _MentionPageBlockState extends State<MentionPageBlock> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getIt<MenuSharedState>().latestOpenView = view;
|
getIt<MenuSharedState>().latestOpenView = view;
|
||||||
|
getIt<TabsBloc>().add(
|
||||||
|
TabsEvent.openPlugin(
|
||||||
|
plugin: view.plugin(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ViewPB?> fetchView(String pageId) async {
|
Future<ViewPB?> fetchView(String pageId) async {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||||
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
import 'package:appflowy/workspace/application/workspace/workspace_listener.dart';
|
import 'package:appflowy/workspace/application/workspace/workspace_listener.dart';
|
||||||
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
|
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
@ -43,7 +44,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
|
|||||||
desc: event.desc ?? "",
|
desc: event.desc ?? "",
|
||||||
);
|
);
|
||||||
result.fold(
|
result.fold(
|
||||||
(app) => {},
|
(app) => emit(state.copyWith(plugin: app.plugin())),
|
||||||
(error) {
|
(error) {
|
||||||
Log.error(error);
|
Log.error(error);
|
||||||
emit(state.copyWith(successOrFailure: right(error)));
|
emit(state.copyWith(successOrFailure: right(error)));
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:appflowy/core/config/kv.dart';
|
||||||
|
import 'package:appflowy/core/config/kv_keys.dart';
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
@ -20,21 +25,34 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
|||||||
super(ViewState.init(view)) {
|
super(ViewState.init(view)) {
|
||||||
on<ViewEvent>((event, emit) async {
|
on<ViewEvent>((event, emit) async {
|
||||||
await event.map(
|
await event.map(
|
||||||
initial: (e) {
|
initial: (e) async {
|
||||||
listener.start(
|
listener.start(
|
||||||
onViewUpdated: (result) {
|
onViewUpdated: (result) {
|
||||||
add(ViewEvent.viewDidUpdate(left(result)));
|
add(ViewEvent.viewDidUpdate(left(result)));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
emit(state);
|
final isExpanded = await _getViewIsExpanded(view);
|
||||||
|
await _loadViewsWhenExpanded(emit, isExpanded);
|
||||||
},
|
},
|
||||||
setIsEditing: (e) {
|
setIsEditing: (e) {
|
||||||
emit(state.copyWith(isEditing: e.isEditing));
|
emit(state.copyWith(isEditing: e.isEditing));
|
||||||
},
|
},
|
||||||
|
setIsExpanded: (e) async {
|
||||||
|
if (e.isExpanded) {
|
||||||
|
await _loadViewsWhenExpanded(emit, true);
|
||||||
|
} else {
|
||||||
|
emit(state.copyWith(isExpanded: e.isExpanded));
|
||||||
|
}
|
||||||
|
await _setViewIsExpanded(view, e.isExpanded);
|
||||||
|
},
|
||||||
viewDidUpdate: (e) {
|
viewDidUpdate: (e) {
|
||||||
e.result.fold(
|
e.result.fold(
|
||||||
(view) => emit(
|
(view) => emit(
|
||||||
state.copyWith(view: view, successOrFailure: left(unit)),
|
state.copyWith(
|
||||||
|
view: view,
|
||||||
|
childViews: view.childViews,
|
||||||
|
successOrFailure: left(unit),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
(error) => emit(
|
(error) => emit(
|
||||||
state.copyWith(successOrFailure: right(error)),
|
state.copyWith(successOrFailure: right(error)),
|
||||||
@ -71,6 +89,36 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
move: (value) async {
|
||||||
|
final result = await ViewBackendService.moveViewV2(
|
||||||
|
viewId: value.from.id,
|
||||||
|
newParentId: value.newParentId,
|
||||||
|
prevViewId: value.prevId,
|
||||||
|
);
|
||||||
|
emit(
|
||||||
|
result.fold(
|
||||||
|
(l) => state.copyWith(successOrFailure: left(unit)),
|
||||||
|
(error) => state.copyWith(successOrFailure: right(error)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
createView: (e) async {
|
||||||
|
final result = await ViewBackendService.createView(
|
||||||
|
parentViewId: view.id,
|
||||||
|
name: e.name,
|
||||||
|
desc: '',
|
||||||
|
layoutType: e.layoutType,
|
||||||
|
initialDataBytes: null,
|
||||||
|
ext: {},
|
||||||
|
openAfterCreate: e.openAfterCreated,
|
||||||
|
);
|
||||||
|
emit(
|
||||||
|
result.fold(
|
||||||
|
(l) => state.copyWith(successOrFailure: left(unit)),
|
||||||
|
(error) => state.copyWith(successOrFailure: right(error)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -80,15 +128,84 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
|||||||
await listener.stop();
|
await listener.stop();
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _loadViewsWhenExpanded(
|
||||||
|
Emitter<ViewState> emit,
|
||||||
|
bool isExpanded,
|
||||||
|
) async {
|
||||||
|
if (!isExpanded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state.childViews.isNotEmpty) {
|
||||||
|
// notify the old child views
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
childViews: state.childViews,
|
||||||
|
isExpanded: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final viewsOrFailed =
|
||||||
|
await ViewBackendService.getChildViews(viewId: state.view.id);
|
||||||
|
viewsOrFailed.fold(
|
||||||
|
(childViews) => emit(
|
||||||
|
state.copyWith(
|
||||||
|
childViews: childViews,
|
||||||
|
isExpanded: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(error) => emit(
|
||||||
|
state.copyWith(
|
||||||
|
successOrFailure: right(error),
|
||||||
|
isExpanded: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _setViewIsExpanded(ViewPB view, bool isExpanded) async {
|
||||||
|
final result = await getIt<KeyValueStorage>().get(KVKeys.expandedViews);
|
||||||
|
final map = result.fold(
|
||||||
|
(l) => {},
|
||||||
|
(r) => jsonDecode(r),
|
||||||
|
);
|
||||||
|
if (isExpanded) {
|
||||||
|
map[view.id] = true;
|
||||||
|
} else {
|
||||||
|
map.remove(view.id);
|
||||||
|
}
|
||||||
|
await getIt<KeyValueStorage>().set(KVKeys.expandedViews, jsonEncode(map));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _getViewIsExpanded(ViewPB view) {
|
||||||
|
return getIt<KeyValueStorage>().get(KVKeys.expandedViews).then((result) {
|
||||||
|
return result.fold((l) => false, (r) {
|
||||||
|
final map = jsonDecode(r);
|
||||||
|
return map[view.id] ?? false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class ViewEvent with _$ViewEvent {
|
class ViewEvent with _$ViewEvent {
|
||||||
const factory ViewEvent.initial() = Initial;
|
const factory ViewEvent.initial() = Initial;
|
||||||
const factory ViewEvent.setIsEditing(bool isEditing) = SetEditing;
|
const factory ViewEvent.setIsEditing(bool isEditing) = SetEditing;
|
||||||
|
const factory ViewEvent.setIsExpanded(bool isExpanded) = SetIsExpanded;
|
||||||
const factory ViewEvent.rename(String newName) = Rename;
|
const factory ViewEvent.rename(String newName) = Rename;
|
||||||
const factory ViewEvent.delete() = Delete;
|
const factory ViewEvent.delete() = Delete;
|
||||||
const factory ViewEvent.duplicate() = Duplicate;
|
const factory ViewEvent.duplicate() = Duplicate;
|
||||||
|
const factory ViewEvent.move(
|
||||||
|
ViewPB from,
|
||||||
|
String newParentId,
|
||||||
|
String? prevId,
|
||||||
|
) = Move;
|
||||||
|
const factory ViewEvent.createView(
|
||||||
|
String name,
|
||||||
|
ViewLayoutPB layoutType, {
|
||||||
|
/// open the view after created
|
||||||
|
@Default(true) bool openAfterCreated,
|
||||||
|
}) = CreateView;
|
||||||
const factory ViewEvent.viewDidUpdate(Either<ViewPB, FlowyError> result) =
|
const factory ViewEvent.viewDidUpdate(Either<ViewPB, FlowyError> result) =
|
||||||
ViewDidUpdate;
|
ViewDidUpdate;
|
||||||
}
|
}
|
||||||
@ -97,12 +214,16 @@ class ViewEvent with _$ViewEvent {
|
|||||||
class ViewState with _$ViewState {
|
class ViewState with _$ViewState {
|
||||||
const factory ViewState({
|
const factory ViewState({
|
||||||
required ViewPB view,
|
required ViewPB view,
|
||||||
|
required List<ViewPB> childViews,
|
||||||
required bool isEditing,
|
required bool isEditing,
|
||||||
|
required bool isExpanded,
|
||||||
required Either<Unit, FlowyError> successOrFailure,
|
required Either<Unit, FlowyError> successOrFailure,
|
||||||
}) = _ViewState;
|
}) = _ViewState;
|
||||||
|
|
||||||
factory ViewState.init(ViewPB view) => ViewState(
|
factory ViewState.init(ViewPB view) => ViewState(
|
||||||
view: view,
|
view: view,
|
||||||
|
childViews: view.childViews,
|
||||||
|
isExpanded: false,
|
||||||
isEditing: false,
|
isEditing: false,
|
||||||
successOrFailure: left(unit),
|
successOrFailure: left(unit),
|
||||||
);
|
);
|
||||||
|
@ -40,11 +40,23 @@ extension FlowyPluginExtension on FlowyPlugin {
|
|||||||
extension ViewExtension on ViewPB {
|
extension ViewExtension on ViewPB {
|
||||||
Widget renderThumbnail({Color? iconColor}) {
|
Widget renderThumbnail({Color? iconColor}) {
|
||||||
const String thumbnail = "file_icon";
|
const String thumbnail = "file_icon";
|
||||||
|
|
||||||
const Widget widget = FlowySvg(name: thumbnail);
|
const Widget widget = FlowySvg(name: thumbnail);
|
||||||
return widget;
|
return widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget icon() {
|
||||||
|
final iconName = switch (layout) {
|
||||||
|
ViewLayoutPB.Board => 'editor/board',
|
||||||
|
ViewLayoutPB.Calendar => 'editor/calendar',
|
||||||
|
ViewLayoutPB.Grid => 'editor/grid',
|
||||||
|
ViewLayoutPB.Document => 'editor/documents',
|
||||||
|
_ => 'file_icon',
|
||||||
|
};
|
||||||
|
return FlowySvg(
|
||||||
|
name: iconName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
PluginType get pluginType {
|
PluginType get pluginType {
|
||||||
switch (layout) {
|
switch (layout) {
|
||||||
case ViewLayoutPB.Board:
|
case ViewLayoutPB.Board:
|
||||||
|
@ -141,6 +141,7 @@ class ViewBackendService {
|
|||||||
return FolderEventUpdateView(payload).send();
|
return FolderEventUpdateView(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deprecated
|
||||||
static Future<Either<Unit, FlowyError>> moveView({
|
static Future<Either<Unit, FlowyError>> moveView({
|
||||||
required String viewId,
|
required String viewId,
|
||||||
required int fromIndex,
|
required int fromIndex,
|
||||||
@ -154,6 +155,24 @@ class ViewBackendService {
|
|||||||
return FolderEventMoveView(payload).send();
|
return FolderEventMoveView(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Move the view to the new parent view.
|
||||||
|
///
|
||||||
|
/// supports nested view
|
||||||
|
/// if the [prevViewId] is null, the view will be moved to the beginning of the list
|
||||||
|
static Future<Either<Unit, FlowyError>> moveViewV2({
|
||||||
|
required String viewId,
|
||||||
|
required String newParentId,
|
||||||
|
required String? prevViewId,
|
||||||
|
}) {
|
||||||
|
final payload = MoveNestedViewPayloadPB(
|
||||||
|
viewId: viewId,
|
||||||
|
newParentId: newParentId,
|
||||||
|
prevViewId: prevViewId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return FolderEventMoveNestedView(payload).send();
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<(ViewPB, List<ViewPB>)>> fetchViewsWithLayoutType(
|
Future<List<(ViewPB, List<ViewPB>)>> fetchViewsWithLayoutType(
|
||||||
ViewLayoutPB? layoutType,
|
ViewLayoutPB? layoutType,
|
||||||
) async {
|
) async {
|
||||||
|
@ -8,6 +8,7 @@ import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
|||||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/hotkeys.dart';
|
import 'package:appflowy/workspace/presentation/home/hotkeys.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/edit_panel/panel_animation.dart';
|
import 'package:appflowy/workspace/presentation/widgets/edit_panel/panel_animation.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/float_bubble/question_bubble.dart';
|
import 'package:appflowy/workspace/presentation/widgets/float_bubble/question_bubble.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
@ -23,7 +24,6 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
import '../widgets/edit_panel/edit_panel.dart';
|
import '../widgets/edit_panel/edit_panel.dart';
|
||||||
import 'home_layout.dart';
|
import 'home_layout.dart';
|
||||||
import 'home_stack.dart';
|
import 'home_stack.dart';
|
||||||
import 'menu/menu.dart';
|
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
final UserProfilePB user;
|
final UserProfilePB user;
|
||||||
@ -118,7 +118,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
buildContext: context,
|
buildContext: context,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final menu = _buildHomeMenu(
|
final menu = _buildHomeSidebar(
|
||||||
layout: layout,
|
layout: layout,
|
||||||
context: context,
|
context: context,
|
||||||
);
|
);
|
||||||
@ -140,16 +140,15 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildHomeMenu({
|
Widget _buildHomeSidebar({
|
||||||
required HomeLayout layout,
|
required HomeLayout layout,
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
}) {
|
}) {
|
||||||
final workspaceSetting = widget.workspaceSetting;
|
final workspaceSetting = widget.workspaceSetting;
|
||||||
final homeMenu = HomeMenu(
|
final homeMenu = HomeSideBar(
|
||||||
user: widget.user,
|
user: widget.user,
|
||||||
workspaceSetting: workspaceSetting,
|
workspaceSetting: workspaceSetting,
|
||||||
);
|
);
|
||||||
|
|
||||||
return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu));
|
return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +83,7 @@ class ImportPanel extends StatelessWidget {
|
|||||||
e.toString(),
|
e.toString(),
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
color: Theme.of(context).colorScheme.tertiary,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await _importFile(parentViewId, e);
|
await _importFile(parentViewId, e);
|
||||||
@ -157,6 +158,8 @@ class ImportPanel extends StatelessWidget {
|
|||||||
assert(false, 'Unsupported Type $importType');
|
assert(false, 'Unsupported Type $importType');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
importCallback(importType, '', null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ enum ImportType {
|
|||||||
}
|
}
|
||||||
return FlowySvg(
|
return FlowySvg(
|
||||||
name: name,
|
name: name,
|
||||||
|
color: Theme.of(context).colorScheme.tertiary,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,13 +3,12 @@ import 'package:appflowy/workspace/application/app/app_bloc.dart';
|
|||||||
import 'package:appflowy/workspace/application/menu/menu_view_section_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/menu_view_section_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:reorderables/reorderables.dart';
|
import 'package:reorderables/reorderables.dart';
|
||||||
|
|
||||||
import 'item.dart';
|
|
||||||
|
|
||||||
class ViewSection extends StatelessWidget {
|
class ViewSection extends StatelessWidget {
|
||||||
final ViewDataContext appViewData;
|
final ViewDataContext appViewData;
|
||||||
const ViewSection({Key? key, required this.appViewData}) : super(key: key);
|
const ViewSection({Key? key, required this.appViewData}) : super(key: key);
|
||||||
|
@ -0,0 +1,114 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class PersonalFolder extends StatefulWidget {
|
||||||
|
const PersonalFolder({
|
||||||
|
super.key,
|
||||||
|
required this.views,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<ViewPB> views;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PersonalFolder> createState() => _PersonalFolderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PersonalFolderState extends State<PersonalFolder> {
|
||||||
|
bool isExpanded = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
PersonalFolderHeader(
|
||||||
|
onPressed: () => setState(
|
||||||
|
() => isExpanded = !isExpanded,
|
||||||
|
),
|
||||||
|
onAdded: () => setState(() => isExpanded = true),
|
||||||
|
),
|
||||||
|
if (isExpanded)
|
||||||
|
...widget.views.map(
|
||||||
|
(view) => ViewItem(
|
||||||
|
key: ValueKey(view.id),
|
||||||
|
isFirstChild: view.id == widget.views.first.id,
|
||||||
|
view: view,
|
||||||
|
level: 0,
|
||||||
|
onSelected: (view) {
|
||||||
|
getIt<MenuSharedState>().latestOpenView = view;
|
||||||
|
context.read<MenuBloc>().add(MenuEvent.openPage(view.plugin()));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PersonalFolderHeader extends StatefulWidget {
|
||||||
|
const PersonalFolderHeader({
|
||||||
|
super.key,
|
||||||
|
required this.onPressed,
|
||||||
|
required this.onAdded,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
final VoidCallback onAdded;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PersonalFolderHeader> createState() => _PersonalFolderHeaderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PersonalFolderHeaderState extends State<PersonalFolderHeader> {
|
||||||
|
bool onHover = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
const iconSize = 26.0;
|
||||||
|
return MouseRegion(
|
||||||
|
onEnter: (event) => setState(() => onHover = true),
|
||||||
|
onExit: (event) => setState(() => onHover = false),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
FlowyTextButton(
|
||||||
|
LocaleKeys.sideBar_personal.tr(),
|
||||||
|
tooltip: LocaleKeys.sideBar_clickToHidePersonal.tr(),
|
||||||
|
constraints: const BoxConstraints(maxHeight: iconSize),
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
fillColor: Colors.transparent,
|
||||||
|
onPressed: widget.onPressed,
|
||||||
|
),
|
||||||
|
if (onHover) ...[
|
||||||
|
const Spacer(),
|
||||||
|
FlowyIconButton(
|
||||||
|
tooltipText: LocaleKeys.sideBar_addAPage.tr(),
|
||||||
|
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
iconPadding: const EdgeInsets.all(2),
|
||||||
|
height: iconSize,
|
||||||
|
width: iconSize,
|
||||||
|
icon: const FlowySvg(name: 'editor/add'),
|
||||||
|
onPressed: () {
|
||||||
|
context.read<MenuBloc>().add(
|
||||||
|
MenuEvent.createApp(
|
||||||
|
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
widget.onAdded();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_folder.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_top_menu.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_trash.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_user.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||||
|
show UserProfilePB;
|
||||||
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
/// Home Sidebar is the left side bar of the home page.
|
||||||
|
///
|
||||||
|
/// in the sidebar, we have:
|
||||||
|
/// - user icon, user name
|
||||||
|
/// - settings
|
||||||
|
/// - scrollable document list
|
||||||
|
/// - trash
|
||||||
|
class HomeSideBar extends StatelessWidget {
|
||||||
|
const HomeSideBar({
|
||||||
|
super.key,
|
||||||
|
required this.user,
|
||||||
|
required this.workspaceSetting,
|
||||||
|
});
|
||||||
|
|
||||||
|
final UserProfilePB user;
|
||||||
|
|
||||||
|
final WorkspaceSettingPB workspaceSetting;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (_) => MenuBloc(
|
||||||
|
user: user,
|
||||||
|
workspace: workspaceSetting.workspace,
|
||||||
|
)..add(const MenuEvent.initial()),
|
||||||
|
child: BlocConsumer<MenuBloc, MenuState>(
|
||||||
|
builder: (context, state) => _buildSidebar(context, state),
|
||||||
|
listenWhen: (p, c) => p.plugin.id != c.plugin.id,
|
||||||
|
listener: (context, state) => getIt<TabsBloc>().add(
|
||||||
|
TabsEvent.openPlugin(plugin: state.plugin),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSidebar(BuildContext context, MenuState state) {
|
||||||
|
final views = state.views;
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
|
border: Border(
|
||||||
|
right: BorderSide(color: Theme.of(context).dividerColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// top menu
|
||||||
|
const SidebarTopMenu(),
|
||||||
|
// user, setting
|
||||||
|
SidebarUser(user: user),
|
||||||
|
// Favorite, Not supported yet
|
||||||
|
const VSpace(20),
|
||||||
|
// scrollable document list
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: SidebarFolder(
|
||||||
|
views: views,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const VSpace(10),
|
||||||
|
// trash
|
||||||
|
const SidebarTrashButton(),
|
||||||
|
const VSpace(10),
|
||||||
|
// new page button
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(left: 6.0),
|
||||||
|
child: SidebarNewPageButton(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/personal_folder.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SidebarFolder extends StatelessWidget {
|
||||||
|
const SidebarFolder({
|
||||||
|
super.key,
|
||||||
|
required this.views,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<ViewPB> views;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// personal
|
||||||
|
PersonalFolder(views: views),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/extension.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class SidebarNewPageButton extends StatelessWidget {
|
||||||
|
const SidebarNewPageButton({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final child = FlowyTextButton(
|
||||||
|
LocaleKeys.newPageText.tr(),
|
||||||
|
fillColor: Colors.transparent,
|
||||||
|
hoverColor: Colors.transparent,
|
||||||
|
fontColor: Theme.of(context).colorScheme.tertiary,
|
||||||
|
onPressed: () async => await _showCreatePageDialog(context),
|
||||||
|
heading: Container(
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
),
|
||||||
|
child: svgWidget('home/new_app'),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
);
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
height: 60,
|
||||||
|
child: TopBorder(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showCreatePageDialog(BuildContext context) async {
|
||||||
|
return NavigatorTextFieldDialog(
|
||||||
|
title: LocaleKeys.newPageText.tr(),
|
||||||
|
value: '',
|
||||||
|
confirm: (value) {
|
||||||
|
if (value.isNotEmpty) {
|
||||||
|
context.read<MenuBloc>().add(MenuEvent.createApp(value, desc: ''));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
).show(context);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
|
import 'package:appflowy/core/frameless_window.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
/// Sidebar top menu is the top bar of the sidebar.
|
||||||
|
///
|
||||||
|
/// in the top menu, we have:
|
||||||
|
/// - appflowy icon (Windows or Linux)
|
||||||
|
/// - close / expand sidebar button
|
||||||
|
class SidebarTopMenu extends StatelessWidget {
|
||||||
|
const SidebarTopMenu({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<MenuBloc, MenuState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return SizedBox(
|
||||||
|
height: HomeSizes.topBarHeight,
|
||||||
|
child: MoveWindowDetector(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_buildLogoIcon(context),
|
||||||
|
const Spacer(),
|
||||||
|
_buildCollapseMenuButton(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLogoIcon(BuildContext context) {
|
||||||
|
if (Platform.isMacOS) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
final name = Theme.of(context).brightness == Brightness.dark
|
||||||
|
? 'flowy_logo_dark_mode'
|
||||||
|
: 'flowy_logo_with_text';
|
||||||
|
return svgWidget(
|
||||||
|
name,
|
||||||
|
size: const Size(92, 17),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCollapseMenuButton(BuildContext context) {
|
||||||
|
final textSpan = TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: '${LocaleKeys.sideBar_closeSidebar.tr()}\n',
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
// TODO(Lucas.Xu): it doesn't work on macOS.
|
||||||
|
text: Platform.isMacOS ? '⌘+\\' : 'Ctrl+\\',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
return Tooltip(
|
||||||
|
richMessage: textSpan,
|
||||||
|
child: FlowyIconButton(
|
||||||
|
width: 28,
|
||||||
|
hoverColor: Colors.transparent,
|
||||||
|
onPressed: () => context
|
||||||
|
.read<HomeSettingBloc>()
|
||||||
|
.add(const HomeSettingEvent.collapseMenu()),
|
||||||
|
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
|
||||||
|
icon: const FlowySvg(
|
||||||
|
name: 'home/hide_menu',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
|
||||||
|
class SidebarTrashButton extends StatelessWidget {
|
||||||
|
const SidebarTrashButton({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ValueListenableBuilder(
|
||||||
|
valueListenable: getIt<MenuSharedState>().notifier,
|
||||||
|
builder: (context, value, child) {
|
||||||
|
return FlowyHover(
|
||||||
|
style: HoverStyle(
|
||||||
|
hoverColor: AFThemeExtension.of(context).greySelect,
|
||||||
|
),
|
||||||
|
isSelected: () => getIt<MenuSharedState>().latestOpenView == null,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 26,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
getIt<MenuSharedState>().latestOpenView = null;
|
||||||
|
getIt<TabsBloc>().add(
|
||||||
|
TabsEvent.openPlugin(
|
||||||
|
plugin: makePlugin(pluginType: PluginType.trash),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: _buildTextButton(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTextButton(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
const HSpace(6),
|
||||||
|
const FlowySvg(
|
||||||
|
size: Size(16, 16),
|
||||||
|
name: 'home/trash',
|
||||||
|
),
|
||||||
|
const HSpace(6),
|
||||||
|
FlowyText.medium(
|
||||||
|
LocaleKeys.trash_text.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||||
|
import 'package:appflowy/startup/entry_point.dart';
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy/util/color_generator/color_generator.dart';
|
||||||
|
import 'package:appflowy/workspace/application/menu/menu_user_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flowy_infra/size.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||||
|
show UserProfilePB;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
class SidebarUser extends StatelessWidget {
|
||||||
|
const SidebarUser({
|
||||||
|
super.key,
|
||||||
|
required this.user,
|
||||||
|
});
|
||||||
|
|
||||||
|
final UserProfilePB user;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider<MenuUserBloc>(
|
||||||
|
create: (context) => getIt<MenuUserBloc>(param1: user)
|
||||||
|
..add(
|
||||||
|
const MenuUserEvent.initial(),
|
||||||
|
),
|
||||||
|
child: BlocBuilder<MenuUserBloc, MenuUserState>(
|
||||||
|
builder: (context, state) => Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
_buildAvatar(context),
|
||||||
|
const HSpace(10),
|
||||||
|
Expanded(
|
||||||
|
child: _buildUserName(context),
|
||||||
|
),
|
||||||
|
_buildSettingsButton(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAvatar(BuildContext context) {
|
||||||
|
String iconUrl = context.read<MenuUserBloc>().state.userProfile.iconUrl;
|
||||||
|
if (iconUrl.isEmpty) {
|
||||||
|
iconUrl = defaultUserAvatar;
|
||||||
|
final String name = _userName(
|
||||||
|
context.read<MenuUserBloc>().state.userProfile,
|
||||||
|
);
|
||||||
|
final Color color = ColorGenerator().generateColorFromString(name);
|
||||||
|
const initialsCount = 2;
|
||||||
|
// Taking the first letters of the name components and limiting to 2 elements
|
||||||
|
final nameInitials = name
|
||||||
|
.split(' ')
|
||||||
|
.where((element) => element.isNotEmpty)
|
||||||
|
.take(initialsCount)
|
||||||
|
.map((element) => element[0].toUpperCase())
|
||||||
|
.join('');
|
||||||
|
return Container(
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: FlowyText.semibold(
|
||||||
|
nameInitials,
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: nameInitials.length == initialsCount ? 12 : 14,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return SizedBox.square(
|
||||||
|
dimension: 25,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: Corners.s5Border,
|
||||||
|
child: CircleAvatar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
child: svgWidget('emoji/$iconUrl'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildUserName(BuildContext context) {
|
||||||
|
final String name = _userName(
|
||||||
|
context.read<MenuUserBloc>().state.userProfile,
|
||||||
|
);
|
||||||
|
return FlowyText.medium(
|
||||||
|
name,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
color: Theme.of(context).colorScheme.tertiary,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSettingsButton(BuildContext context) {
|
||||||
|
final userProfile = context.read<MenuUserBloc>().state.userProfile;
|
||||||
|
return Tooltip(
|
||||||
|
message: LocaleKeys.settings_menu_open.tr(),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return BlocProvider<DocumentAppearanceCubit>.value(
|
||||||
|
value: BlocProvider.of<DocumentAppearanceCubit>(context),
|
||||||
|
child: SettingsDialog(
|
||||||
|
userProfile,
|
||||||
|
didLogout: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
await FlowyRunner.run(
|
||||||
|
FlowyApp(),
|
||||||
|
integrationEnv(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
dismissDialog: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: SizedBox.square(
|
||||||
|
dimension: 20,
|
||||||
|
child: svgWidget(
|
||||||
|
'home/settings',
|
||||||
|
color: Theme.of(context).colorScheme.tertiary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the user name, if the user name is empty, return the default user name.
|
||||||
|
String _userName(UserProfilePB userProfile) {
|
||||||
|
String name = userProfile.name;
|
||||||
|
if (name.isEmpty) {
|
||||||
|
name = LocaleKeys.defaultUsername.tr();
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,172 @@
|
|||||||
|
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/draggable_item/draggable_item.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
enum DraggableHoverPosition {
|
||||||
|
none,
|
||||||
|
top,
|
||||||
|
center,
|
||||||
|
bottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
class DraggableViewItem extends StatefulWidget {
|
||||||
|
const DraggableViewItem({
|
||||||
|
super.key,
|
||||||
|
required this.view,
|
||||||
|
this.feedback,
|
||||||
|
required this.child,
|
||||||
|
this.isFirstChild = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final WidgetBuilder? feedback;
|
||||||
|
final ViewPB view;
|
||||||
|
final bool isFirstChild;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DraggableViewItem> createState() => _DraggableViewItemState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DraggableViewItemState extends State<DraggableViewItem> {
|
||||||
|
DraggableHoverPosition position = DraggableHoverPosition.none;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// add top border if the draggable item is on the top of the list
|
||||||
|
// highlight the draggable item if the draggable item is on the center
|
||||||
|
// add bottom border if the draggable item is on the bottom of the list
|
||||||
|
final child = Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// only show the top border when the draggable item is the first child
|
||||||
|
if (widget.isFirstChild)
|
||||||
|
Divider(
|
||||||
|
height: 2,
|
||||||
|
thickness: 2,
|
||||||
|
color: position == DraggableHoverPosition.top
|
||||||
|
? Theme.of(context).colorScheme.secondary
|
||||||
|
: Colors.transparent,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
color: position == DraggableHoverPosition.center
|
||||||
|
? Theme.of(context).colorScheme.secondary.withOpacity(0.5)
|
||||||
|
: Colors.transparent,
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
|
Divider(
|
||||||
|
height: 2,
|
||||||
|
thickness: 2,
|
||||||
|
color: position == DraggableHoverPosition.bottom
|
||||||
|
? Theme.of(context).colorScheme.secondary
|
||||||
|
: Colors.transparent,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return DraggableItem<ViewPB>(
|
||||||
|
data: widget.view,
|
||||||
|
onWillAccept: (data) => true,
|
||||||
|
onMove: (data) {
|
||||||
|
if (!_shouldAccept(data.data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final renderBox = context.findRenderObject() as RenderBox;
|
||||||
|
final offset = renderBox.globalToLocal(data.offset);
|
||||||
|
setState(() {
|
||||||
|
position = _computeHoverPosition(offset, renderBox.size);
|
||||||
|
Log.debug(
|
||||||
|
'offset: $offset, position: $position, size: ${renderBox.size}',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onLeave: (_) => setState(
|
||||||
|
() => position = DraggableHoverPosition.none,
|
||||||
|
),
|
||||||
|
onAccept: (data) {
|
||||||
|
_move(data, widget.view);
|
||||||
|
setState(
|
||||||
|
() => position = DraggableHoverPosition.none,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
feedback: IntrinsicWidth(
|
||||||
|
child: Opacity(
|
||||||
|
opacity: 0.5,
|
||||||
|
child: widget.feedback?.call(context) ?? child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _move(ViewPB from, ViewPB to) {
|
||||||
|
switch (position) {
|
||||||
|
case DraggableHoverPosition.top:
|
||||||
|
context.read<ViewBloc>().add(
|
||||||
|
ViewEvent.move(
|
||||||
|
from,
|
||||||
|
to.parentViewId,
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case DraggableHoverPosition.bottom:
|
||||||
|
context.read<ViewBloc>().add(
|
||||||
|
ViewEvent.move(
|
||||||
|
from,
|
||||||
|
to.parentViewId,
|
||||||
|
to.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case DraggableHoverPosition.center:
|
||||||
|
context.read<ViewBloc>().add(
|
||||||
|
ViewEvent.move(
|
||||||
|
from,
|
||||||
|
to.id,
|
||||||
|
to.childViews.lastOrNull?.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case DraggableHoverPosition.none:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DraggableHoverPosition _computeHoverPosition(Offset offset, Size size) {
|
||||||
|
final threshold = size.height / 4.0;
|
||||||
|
if (widget.isFirstChild && offset.dy < -5.0) {
|
||||||
|
return DraggableHoverPosition.top;
|
||||||
|
}
|
||||||
|
if (offset.dy > threshold) {
|
||||||
|
return DraggableHoverPosition.bottom;
|
||||||
|
}
|
||||||
|
return DraggableHoverPosition.center;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _shouldAccept(ViewPB data) {
|
||||||
|
// ignore moving the view to itself
|
||||||
|
if (data.id == widget.view.id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore moving the view to its child view
|
||||||
|
if (data.containsView(widget.view)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on ViewPB {
|
||||||
|
bool containsView(ViewPB view) {
|
||||||
|
if (id == view.id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return childViews.any((v) => v.containsView(view));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
enum ViewMoreActionType {
|
||||||
|
delete,
|
||||||
|
addToFavorites, // not supported yet.
|
||||||
|
duplicate,
|
||||||
|
copyLink, // not supported yet.
|
||||||
|
rename,
|
||||||
|
moveTo, // not supported yet.
|
||||||
|
openInNewTab,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ViewMoreActionTypeExtension on ViewMoreActionType {
|
||||||
|
String get name {
|
||||||
|
switch (this) {
|
||||||
|
case ViewMoreActionType.delete:
|
||||||
|
return LocaleKeys.disclosureAction_delete.tr();
|
||||||
|
case ViewMoreActionType.addToFavorites:
|
||||||
|
return LocaleKeys.disclosureAction_addToFavorites.tr();
|
||||||
|
case ViewMoreActionType.duplicate:
|
||||||
|
return LocaleKeys.disclosureAction_duplicate.tr();
|
||||||
|
case ViewMoreActionType.copyLink:
|
||||||
|
return LocaleKeys.disclosureAction_copyLink.tr();
|
||||||
|
case ViewMoreActionType.rename:
|
||||||
|
return LocaleKeys.disclosureAction_rename.tr();
|
||||||
|
case ViewMoreActionType.moveTo:
|
||||||
|
return LocaleKeys.disclosureAction_moveTo.tr();
|
||||||
|
case ViewMoreActionType.openInNewTab:
|
||||||
|
return LocaleKeys.disclosureAction_openNewTab.tr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget icon(Color iconColor) {
|
||||||
|
switch (this) {
|
||||||
|
case ViewMoreActionType.delete:
|
||||||
|
return const FlowySvg(name: 'editor/delete');
|
||||||
|
case ViewMoreActionType.addToFavorites:
|
||||||
|
return const Icon(Icons.favorite);
|
||||||
|
case ViewMoreActionType.duplicate:
|
||||||
|
return const FlowySvg(name: 'editor/copy');
|
||||||
|
case ViewMoreActionType.copyLink:
|
||||||
|
return const Icon(Icons.copy);
|
||||||
|
case ViewMoreActionType.rename:
|
||||||
|
return const FlowySvg(name: 'editor/edit');
|
||||||
|
case ViewMoreActionType.moveTo:
|
||||||
|
return const Icon(Icons.move_to_inbox);
|
||||||
|
case ViewMoreActionType.openInNewTab:
|
||||||
|
return const FlowySvg(name: 'grid/expander');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,130 @@
|
|||||||
|
import 'package:appflowy/plugins/document/document.dart';
|
||||||
|
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/app/header/import/import_panel.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
class ViewAddButton extends StatelessWidget {
|
||||||
|
const ViewAddButton({
|
||||||
|
super.key,
|
||||||
|
required this.parentViewId,
|
||||||
|
required this.onEditing,
|
||||||
|
required this.onSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String parentViewId;
|
||||||
|
final void Function(bool value) onEditing;
|
||||||
|
final Function(
|
||||||
|
PluginBuilder,
|
||||||
|
String? name,
|
||||||
|
List<int>? initialDataBytes,
|
||||||
|
bool openAfterCreated,
|
||||||
|
bool createNewView,
|
||||||
|
) onSelected;
|
||||||
|
|
||||||
|
List<PopoverAction> get _actions {
|
||||||
|
return [
|
||||||
|
// document, grid, kanban, calendar
|
||||||
|
...pluginBuilders().map(
|
||||||
|
(pluginBuilder) => ViewAddButtonActionWrapper(
|
||||||
|
pluginBuilder: pluginBuilder,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// import from ...
|
||||||
|
...getIt<PluginSandbox>().builders.whereType<DocumentPluginBuilder>().map(
|
||||||
|
(pluginBuilder) => ViewImportActionWrapper(
|
||||||
|
pluginBuilder: pluginBuilder,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return PopoverActionList<PopoverAction>(
|
||||||
|
direction: PopoverDirection.bottomWithLeftAligned,
|
||||||
|
actions: _actions,
|
||||||
|
offset: const Offset(0, 8),
|
||||||
|
buildChild: (popover) {
|
||||||
|
return FlowyIconButton(
|
||||||
|
hoverColor: Colors.transparent,
|
||||||
|
iconPadding: const EdgeInsets.all(2),
|
||||||
|
width: 26,
|
||||||
|
icon: const FlowySvg(name: 'editor/add'),
|
||||||
|
onPressed: () {
|
||||||
|
onEditing(true);
|
||||||
|
popover.show();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSelected: (action, popover) {
|
||||||
|
onEditing(false);
|
||||||
|
if (action is ViewAddButtonActionWrapper) {
|
||||||
|
_showViewAddButtonActions(context, action);
|
||||||
|
} else if (action is ViewImportActionWrapper) {
|
||||||
|
_showViewImportAction(context, action);
|
||||||
|
}
|
||||||
|
popover.close();
|
||||||
|
},
|
||||||
|
onClosed: () {
|
||||||
|
onEditing(false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showViewAddButtonActions(
|
||||||
|
BuildContext context,
|
||||||
|
ViewAddButtonActionWrapper action,
|
||||||
|
) {
|
||||||
|
onSelected(action.pluginBuilder, null, null, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showViewImportAction(
|
||||||
|
BuildContext context,
|
||||||
|
ViewImportActionWrapper action,
|
||||||
|
) {
|
||||||
|
showImportPanel(
|
||||||
|
parentViewId,
|
||||||
|
context,
|
||||||
|
(type, name, initialDataBytes) {
|
||||||
|
onSelected(action.pluginBuilder, null, null, true, false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewAddButtonActionWrapper extends ActionCell {
|
||||||
|
ViewAddButtonActionWrapper({
|
||||||
|
required this.pluginBuilder,
|
||||||
|
});
|
||||||
|
|
||||||
|
final PluginBuilder pluginBuilder;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget? leftIcon(Color iconColor) => FlowySvg(name: pluginBuilder.menuIcon);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => pluginBuilder.menuName;
|
||||||
|
|
||||||
|
PluginType get pluginType => pluginBuilder.pluginType;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewImportActionWrapper extends ActionCell {
|
||||||
|
ViewImportActionWrapper({
|
||||||
|
required this.pluginBuilder,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DocumentPluginBuilder pluginBuilder;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget? leftIcon(Color iconColor) => const FlowySvg(name: 'editor/import');
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => LocaleKeys.moreAction_import.tr();
|
||||||
|
}
|
@ -0,0 +1,322 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class ViewItem extends StatelessWidget {
|
||||||
|
const ViewItem({
|
||||||
|
super.key,
|
||||||
|
required this.view,
|
||||||
|
required this.level,
|
||||||
|
this.leftPadding = 10,
|
||||||
|
required this.onSelected,
|
||||||
|
this.isFirstChild = false,
|
||||||
|
this.isDraggable = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ViewPB view;
|
||||||
|
|
||||||
|
// indicate the level of the view item
|
||||||
|
// used to calculate the left padding
|
||||||
|
final int level;
|
||||||
|
|
||||||
|
// the left padding of the view item for each level
|
||||||
|
// the left padding of the each level = level * leftPadding
|
||||||
|
final double leftPadding;
|
||||||
|
|
||||||
|
final void Function(ViewPB) onSelected;
|
||||||
|
|
||||||
|
// used for indicating the first child of the parent view, so that we can
|
||||||
|
// add top border to the first child
|
||||||
|
final bool isFirstChild;
|
||||||
|
|
||||||
|
// it should be false when it's rendered as feedback widget inside DraggableItem
|
||||||
|
final bool isDraggable;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (_) => ViewBloc(view: view)..add(const ViewEvent.initial()),
|
||||||
|
child: BlocBuilder<ViewBloc, ViewState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
view.childViews
|
||||||
|
..clear()
|
||||||
|
..addAll(state.childViews);
|
||||||
|
return InnerViewItem(
|
||||||
|
view: view,
|
||||||
|
level: level,
|
||||||
|
leftPadding: leftPadding,
|
||||||
|
showActions: state.isEditing,
|
||||||
|
isExpanded: state.isExpanded,
|
||||||
|
onSelected: onSelected,
|
||||||
|
isFirstChild: isFirstChild,
|
||||||
|
isDraggable: isDraggable,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InnerViewItem extends StatelessWidget {
|
||||||
|
const InnerViewItem({
|
||||||
|
super.key,
|
||||||
|
required this.view,
|
||||||
|
this.isDraggable = true,
|
||||||
|
this.isExpanded = true,
|
||||||
|
required this.level,
|
||||||
|
this.leftPadding = 10,
|
||||||
|
required this.showActions,
|
||||||
|
required this.onSelected,
|
||||||
|
this.isFirstChild = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ViewPB view;
|
||||||
|
|
||||||
|
final bool isDraggable;
|
||||||
|
final bool isExpanded;
|
||||||
|
final bool isFirstChild;
|
||||||
|
|
||||||
|
final int level;
|
||||||
|
final double leftPadding;
|
||||||
|
|
||||||
|
final bool showActions;
|
||||||
|
final void Function(ViewPB) onSelected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget child = SingleInnerViewItem(
|
||||||
|
view: view,
|
||||||
|
level: level,
|
||||||
|
showActions: showActions,
|
||||||
|
onSelected: onSelected,
|
||||||
|
isExpanded: isExpanded,
|
||||||
|
);
|
||||||
|
|
||||||
|
// if the view is expanded and has child views, render its child views
|
||||||
|
final childViews = view.childViews;
|
||||||
|
if (isExpanded && childViews.isNotEmpty) {
|
||||||
|
final children = childViews.map((childView) {
|
||||||
|
return ViewItem(
|
||||||
|
key: ValueKey(childView.id),
|
||||||
|
isFirstChild: childView.id == childViews.first.id,
|
||||||
|
view: childView,
|
||||||
|
level: level + 1,
|
||||||
|
onSelected: onSelected,
|
||||||
|
isDraggable: isDraggable,
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
child = Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
child,
|
||||||
|
...children,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap the child with DraggableItem if isDraggable is true
|
||||||
|
if (isDraggable) {
|
||||||
|
child = DraggableViewItem(
|
||||||
|
isFirstChild: isFirstChild,
|
||||||
|
view: view,
|
||||||
|
child: child,
|
||||||
|
feedback: (context) {
|
||||||
|
return ViewItem(
|
||||||
|
view: view,
|
||||||
|
level: level,
|
||||||
|
onSelected: onSelected,
|
||||||
|
isDraggable: false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SingleInnerViewItem extends StatefulWidget {
|
||||||
|
const SingleInnerViewItem({
|
||||||
|
super.key,
|
||||||
|
required this.view,
|
||||||
|
required this.isExpanded,
|
||||||
|
required this.level,
|
||||||
|
this.leftPadding = 10,
|
||||||
|
required this.showActions,
|
||||||
|
required this.onSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ViewPB view;
|
||||||
|
final bool isExpanded;
|
||||||
|
|
||||||
|
final int level;
|
||||||
|
final double leftPadding;
|
||||||
|
|
||||||
|
final bool showActions;
|
||||||
|
final void Function(ViewPB) onSelected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SingleInnerViewItem> createState() => _SingleInnerViewItemState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FlowyHover(
|
||||||
|
style: HoverStyle(
|
||||||
|
hoverColor: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
buildWhenOnHover: () => !widget.showActions,
|
||||||
|
builder: (_, onHover) => _buildViewItem(onHover),
|
||||||
|
isSelected: () =>
|
||||||
|
widget.showActions ||
|
||||||
|
getIt<MenuSharedState>().latestOpenView?.id == widget.view.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildViewItem(bool onHover) {
|
||||||
|
final children = [
|
||||||
|
// expand icon
|
||||||
|
_buildExpandedIcon(),
|
||||||
|
const HSpace(7),
|
||||||
|
// icon
|
||||||
|
SizedBox.square(
|
||||||
|
dimension: 16,
|
||||||
|
child: widget.view.icon(),
|
||||||
|
),
|
||||||
|
const HSpace(5),
|
||||||
|
// title
|
||||||
|
Expanded(
|
||||||
|
child: FlowyText.regular(
|
||||||
|
widget.view.name,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
// hover action
|
||||||
|
if (widget.showActions || onHover) {
|
||||||
|
// ··· more action button
|
||||||
|
children.add(_buildViewMoreActionButton(context));
|
||||||
|
// + button
|
||||||
|
children.add(_buildViewAddButton(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => widget.onSelected(widget.view),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 26,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(left: widget.level * widget.leftPadding),
|
||||||
|
child: Row(
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// > button
|
||||||
|
Widget _buildExpandedIcon() {
|
||||||
|
final name =
|
||||||
|
widget.isExpanded ? 'home/drop_down_show' : 'home/drop_down_hide';
|
||||||
|
return GestureDetector(
|
||||||
|
child: FlowySvg(
|
||||||
|
name: name,
|
||||||
|
size: const Size.square(16.0),
|
||||||
|
),
|
||||||
|
onTap: () => context
|
||||||
|
.read<ViewBloc>()
|
||||||
|
.add(ViewEvent.setIsExpanded(!widget.isExpanded)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// + button
|
||||||
|
Widget _buildViewAddButton(BuildContext context) {
|
||||||
|
return Tooltip(
|
||||||
|
message: LocaleKeys.menuAppHeader_addPageTooltip.tr(),
|
||||||
|
child: ViewAddButton(
|
||||||
|
parentViewId: widget.view.id,
|
||||||
|
onEditing: (value) =>
|
||||||
|
context.read<ViewBloc>().add(ViewEvent.setIsEditing(value)),
|
||||||
|
onSelected: (
|
||||||
|
pluginBuilder,
|
||||||
|
name,
|
||||||
|
initialDataBytes,
|
||||||
|
openAfterCreated,
|
||||||
|
createNewView,
|
||||||
|
) {
|
||||||
|
if (createNewView) {
|
||||||
|
context.read<ViewBloc>().add(
|
||||||
|
ViewEvent.createView(
|
||||||
|
name ?? LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||||
|
pluginBuilder.layoutType!,
|
||||||
|
openAfterCreated: openAfterCreated,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
context.read<ViewBloc>().add(
|
||||||
|
const ViewEvent.setIsExpanded(true),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ··· more action button
|
||||||
|
Widget _buildViewMoreActionButton(BuildContext context) {
|
||||||
|
return Tooltip(
|
||||||
|
message: LocaleKeys.menuAppHeader_moreButtonToolTip.tr(),
|
||||||
|
child: ViewMoreActionButton(
|
||||||
|
onEditing: (value) =>
|
||||||
|
context.read<ViewBloc>().add(ViewEvent.setIsEditing(value)),
|
||||||
|
onAction: (action) {
|
||||||
|
switch (action) {
|
||||||
|
case ViewMoreActionType.rename:
|
||||||
|
NavigatorTextFieldDialog(
|
||||||
|
title: LocaleKeys.disclosureAction_rename.tr(),
|
||||||
|
autoSelectAllText: true,
|
||||||
|
value: widget.view.name,
|
||||||
|
confirm: (newValue) {
|
||||||
|
context.read<ViewBloc>().add(ViewEvent.rename(newValue));
|
||||||
|
},
|
||||||
|
).show(context);
|
||||||
|
break;
|
||||||
|
case ViewMoreActionType.delete:
|
||||||
|
context.read<ViewBloc>().add(const ViewEvent.delete());
|
||||||
|
break;
|
||||||
|
case ViewMoreActionType.duplicate:
|
||||||
|
context.read<ViewBloc>().add(const ViewEvent.duplicate());
|
||||||
|
break;
|
||||||
|
case ViewMoreActionType.openInNewTab:
|
||||||
|
context.read<TabsBloc>().add(
|
||||||
|
TabsEvent.openTab(
|
||||||
|
plugin: widget.view.plugin(),
|
||||||
|
view: widget.view,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw UnsupportedError('$action is not supported');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||||
|
|
||||||
|
const supportedActionTypes = [
|
||||||
|
ViewMoreActionType.rename,
|
||||||
|
ViewMoreActionType.delete,
|
||||||
|
ViewMoreActionType.duplicate,
|
||||||
|
ViewMoreActionType.openInNewTab,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// ··· button beside the view name
|
||||||
|
class ViewMoreActionButton extends StatelessWidget {
|
||||||
|
const ViewMoreActionButton({
|
||||||
|
super.key,
|
||||||
|
required this.onEditing,
|
||||||
|
required this.onAction,
|
||||||
|
});
|
||||||
|
|
||||||
|
final void Function(bool value) onEditing;
|
||||||
|
final void Function(ViewMoreActionType) onAction;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return PopoverActionList<ViewMoreActionTypeWrapper>(
|
||||||
|
direction: PopoverDirection.bottomWithCenterAligned,
|
||||||
|
offset: const Offset(0, 8),
|
||||||
|
actions: supportedActionTypes
|
||||||
|
.map((e) => ViewMoreActionTypeWrapper(e))
|
||||||
|
.toList(),
|
||||||
|
buildChild: (popover) {
|
||||||
|
return FlowyIconButton(
|
||||||
|
hoverColor: Colors.transparent,
|
||||||
|
iconPadding: const EdgeInsets.all(2),
|
||||||
|
width: 26,
|
||||||
|
icon: const FlowySvg(name: 'editor/details'),
|
||||||
|
onPressed: () {
|
||||||
|
onEditing(true);
|
||||||
|
popover.show();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSelected: (action, popover) {
|
||||||
|
onEditing(false);
|
||||||
|
onAction(action.inner);
|
||||||
|
popover.close();
|
||||||
|
},
|
||||||
|
onClosed: () => onEditing(false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewMoreActionTypeWrapper extends ActionCell {
|
||||||
|
ViewMoreActionTypeWrapper(this.inner);
|
||||||
|
|
||||||
|
final ViewMoreActionType inner;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget? leftIcon(Color iconColor) => inner.icon(iconColor);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => inner.name;
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class DraggableItem<T extends Object> extends StatefulWidget {
|
||||||
|
const DraggableItem({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
required this.data,
|
||||||
|
this.feedback,
|
||||||
|
this.childWhenDragging,
|
||||||
|
this.onAccept,
|
||||||
|
this.onWillAccept,
|
||||||
|
this.onMove,
|
||||||
|
this.onLeave,
|
||||||
|
this.enableAutoScroll = true,
|
||||||
|
this.hitTestSize = const Size(100, 100),
|
||||||
|
});
|
||||||
|
|
||||||
|
final T data;
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final Widget? feedback;
|
||||||
|
final Widget? childWhenDragging;
|
||||||
|
|
||||||
|
final DragTargetAccept<T>? onAccept;
|
||||||
|
final DragTargetWillAccept<T>? onWillAccept;
|
||||||
|
final DragTargetMove<T>? onMove;
|
||||||
|
final DragTargetLeave<T>? onLeave;
|
||||||
|
|
||||||
|
/// Whether to enable auto scroll when dragging.
|
||||||
|
///
|
||||||
|
/// If true, the draggable item must be wrapped inside a [Scrollable] widget.
|
||||||
|
final bool enableAutoScroll;
|
||||||
|
final Size hitTestSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DraggableItem<T>> createState() => _DraggableItemState<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DraggableItemState<T extends Object> extends State<DraggableItem<T>> {
|
||||||
|
ScrollableState? scrollable;
|
||||||
|
EdgeDraggingAutoScroller? autoScroller;
|
||||||
|
Rect? dragTarget;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
|
||||||
|
initAutoScrollerIfNeeded(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
initAutoScrollerIfNeeded(context);
|
||||||
|
|
||||||
|
return DragTarget(
|
||||||
|
onAccept: widget.onAccept,
|
||||||
|
onWillAccept: widget.onWillAccept,
|
||||||
|
onMove: widget.onMove,
|
||||||
|
onLeave: widget.onLeave,
|
||||||
|
builder: (_, __, ___) => Draggable<T>(
|
||||||
|
data: widget.data,
|
||||||
|
feedback: widget.feedback ?? widget.child,
|
||||||
|
childWhenDragging: widget.childWhenDragging ?? widget.child,
|
||||||
|
child: widget.child,
|
||||||
|
onDragUpdate: (details) {
|
||||||
|
if (widget.enableAutoScroll) {
|
||||||
|
dragTarget = details.globalPosition & widget.hitTestSize;
|
||||||
|
autoScroller?.startAutoScrollIfNecessary(dragTarget!);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragEnd: (details) {
|
||||||
|
autoScroller?.stopAutoScroll();
|
||||||
|
dragTarget = null;
|
||||||
|
},
|
||||||
|
onDraggableCanceled: (_, __) {
|
||||||
|
autoScroller?.stopAutoScroll();
|
||||||
|
dragTarget = null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void initAutoScrollerIfNeeded(BuildContext context) {
|
||||||
|
if (!widget.enableAutoScroll) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollable = Scrollable.of(context);
|
||||||
|
if (scrollable == null) {
|
||||||
|
throw FlutterError(
|
||||||
|
'DraggableItem must be wrapped inside a Scrollable widget '
|
||||||
|
'when enableAutoScroll is true.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
autoScroller?.stopAutoScroll();
|
||||||
|
autoScroller = EdgeDraggingAutoScroller(
|
||||||
|
scrollable!,
|
||||||
|
onScrollViewScrolled: () {
|
||||||
|
if (dragTarget != null) {
|
||||||
|
autoScroller!.startAutoScrollIfNecessary(dragTarget!);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
velocityScalar: 20,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -24,3 +24,28 @@ extension FlowyStyledWidget on Widget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TopBorder extends StatelessWidget {
|
||||||
|
const TopBorder({
|
||||||
|
super.key,
|
||||||
|
this.width = 1.0,
|
||||||
|
this.color = Colors.grey,
|
||||||
|
required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final double width;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(width: width, color: color),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -69,4 +69,32 @@ void main() {
|
|||||||
|
|
||||||
assert(appBloc.state.views.isEmpty);
|
assert(appBloc.state.views.isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('create nested view test', () async {
|
||||||
|
final app = await testContext.createTestApp();
|
||||||
|
|
||||||
|
final appBloc = AppBloc(view: app);
|
||||||
|
appBloc
|
||||||
|
..add(
|
||||||
|
const AppEvent.initial(),
|
||||||
|
)
|
||||||
|
..add(
|
||||||
|
const AppEvent.createView('Document 1', ViewLayoutPB.Document),
|
||||||
|
);
|
||||||
|
await blocResponseFuture();
|
||||||
|
|
||||||
|
// create a nested view
|
||||||
|
const name = 'Document 1 - 1';
|
||||||
|
final viewBloc = ViewBloc(view: appBloc.state.views.first);
|
||||||
|
viewBloc
|
||||||
|
..add(
|
||||||
|
const ViewEvent.initial(),
|
||||||
|
)
|
||||||
|
..add(
|
||||||
|
const ViewEvent.createView(name, ViewLayoutPB.Document),
|
||||||
|
);
|
||||||
|
await blocResponseFuture();
|
||||||
|
|
||||||
|
assert(viewBloc.state.childViews.first.name == name);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,10 @@
|
|||||||
"rename": "Rename",
|
"rename": "Rename",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"duplicate": "Duplicate",
|
"duplicate": "Duplicate",
|
||||||
"openNewTab": "Open in a new tab"
|
"openNewTab": "Open in a new tab",
|
||||||
|
"moveTo": "Move to",
|
||||||
|
"addToFavorites": "Add to Favorites",
|
||||||
|
"copyLink": "Copy Link"
|
||||||
},
|
},
|
||||||
"blankPageTitle": "Blank page",
|
"blankPageTitle": "Blank page",
|
||||||
"newPageText": "New page",
|
"newPageText": "New page",
|
||||||
@ -111,6 +114,7 @@
|
|||||||
"feedback": "Feedback"
|
"feedback": "Feedback"
|
||||||
},
|
},
|
||||||
"menuAppHeader": {
|
"menuAppHeader": {
|
||||||
|
"moreButtonToolTip": "Remove, rename, and more...",
|
||||||
"addPageTooltip": "Quickly add a page inside",
|
"addPageTooltip": "Quickly add a page inside",
|
||||||
"defaultNewPageName": "Untitled",
|
"defaultNewPageName": "Untitled",
|
||||||
"renameDialog": "Rename"
|
"renameDialog": "Rename"
|
||||||
@ -146,7 +150,11 @@
|
|||||||
},
|
},
|
||||||
"sideBar": {
|
"sideBar": {
|
||||||
"closeSidebar": "Close side bar",
|
"closeSidebar": "Close side bar",
|
||||||
"openSidebar": "Open side bar"
|
"openSidebar": "Open side bar",
|
||||||
|
"personal": "Personal",
|
||||||
|
"favorites": "Favorites",
|
||||||
|
"clickToHidePersonal": "Click to hide personal section",
|
||||||
|
"addAPage": "Add a page"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"export": {
|
"export": {
|
||||||
|
92
frontend/rust-lib/Cargo.lock
generated
92
frontend/rust-lib/Cargo.lock
generated
@ -178,9 +178,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-config"
|
name = "aws-config"
|
||||||
version = "0.55.3"
|
version = "0.55.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bcdcf0d683fe9c23d32cf5b53c9918ea0a500375a9fb20109802552658e576c9"
|
checksum = "fc00553f5f3c06ffd4510a9d576f92143618706c45ea6ff81e84ad9be9588abd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-credential-types",
|
"aws-credential-types",
|
||||||
"aws-http",
|
"aws-http",
|
||||||
@ -208,9 +208,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-credential-types"
|
name = "aws-credential-types"
|
||||||
version = "0.55.3"
|
version = "0.55.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fcdb2f7acbc076ff5ad05e7864bdb191ca70a6fd07668dc3a1a8bcd051de5ae"
|
checksum = "4cb57ac6088805821f78d282c0ba8aec809f11cbee10dda19a97b03ab040ccc2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-smithy-async",
|
"aws-smithy-async",
|
||||||
"aws-smithy-types",
|
"aws-smithy-types",
|
||||||
@ -222,9 +222,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-endpoint"
|
name = "aws-endpoint"
|
||||||
version = "0.55.3"
|
version = "0.55.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8cce1c41a6cfaa726adee9ebb9a56fcd2bbfd8be49fd8a04c5e20fd968330b04"
|
checksum = "9c5f6f84a4f46f95a9bb71d9300b73cd67eb868bc43ae84f66ad34752299f4ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-smithy-http",
|
"aws-smithy-http",
|
||||||
"aws-smithy-types",
|
"aws-smithy-types",
|
||||||
@ -236,9 +236,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-http"
|
name = "aws-http"
|
||||||
version = "0.55.3"
|
version = "0.55.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aadbc44e7a8f3e71c8b374e03ecd972869eb91dd2bc89ed018954a52ba84bc44"
|
checksum = "a754683c322f7dc5167484266489fdebdcd04d26e53c162cad1f3f949f2c5671"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-credential-types",
|
"aws-credential-types",
|
||||||
"aws-smithy-http",
|
"aws-smithy-http",
|
||||||
@ -281,9 +281,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-sdk-sso"
|
name = "aws-sdk-sso"
|
||||||
version = "0.28.0"
|
version = "0.27.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8b812340d86d4a766b2ca73f740dfd47a97c2dff0c06c8517a16d88241957e4"
|
checksum = "babfd626348836a31785775e3c08a4c345a5ab4c6e06dfd9167f2bee0e6295d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-credential-types",
|
"aws-credential-types",
|
||||||
"aws-endpoint",
|
"aws-endpoint",
|
||||||
@ -306,9 +306,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-sdk-sts"
|
name = "aws-sdk-sts"
|
||||||
version = "0.28.0"
|
version = "0.27.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "265fac131fbfc188e5c3d96652ea90ecc676a934e3174eaaee523c6cec040b3b"
|
checksum = "2d0fbe3c2c342bc8dfea4bb43937405a8ec06f99140a0dcb9c7b59e54dfa93a1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-credential-types",
|
"aws-credential-types",
|
||||||
"aws-endpoint",
|
"aws-endpoint",
|
||||||
@ -332,9 +332,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-sig-auth"
|
name = "aws-sig-auth"
|
||||||
version = "0.55.3"
|
version = "0.55.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b94acb10af0c879ecd5c7bdf51cda6679a0a4f4643ce630905a77673bfa3c61"
|
checksum = "84dc92a63ede3c2cbe43529cb87ffa58763520c96c6a46ca1ced80417afba845"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-credential-types",
|
"aws-credential-types",
|
||||||
"aws-sigv4",
|
"aws-sigv4",
|
||||||
@ -346,9 +346,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-sigv4"
|
name = "aws-sigv4"
|
||||||
version = "0.55.3"
|
version = "0.55.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d2ce6f507be68e968a33485ced670111d1cbad161ddbbab1e313c03d37d8f4c"
|
checksum = "392fefab9d6fcbd76d518eb3b1c040b84728ab50f58df0c3c53ada4bea9d327e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-smithy-http",
|
"aws-smithy-http",
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
@ -365,9 +365,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-smithy-async"
|
name = "aws-smithy-async"
|
||||||
version = "0.55.3"
|
version = "0.55.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13bda3996044c202d75b91afeb11a9afae9db9a721c6a7a427410018e286b880"
|
checksum = "ae23b9fe7a07d0919000116c4c5c0578303fbce6fc8d32efca1f7759d4c20faf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@ -377,9 +377,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-smithy-client"
|
name = "aws-smithy-client"
|
||||||
version = "0.55.3"
|
version = "0.55.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0a86aa6e21e86c4252ad6a0e3e74da9617295d8d6e374d552be7d3059c41cedd"
|
checksum = "5230d25d244a51339273b8870f0f77874cd4449fb4f8f629b21188ae10cfc0ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-smithy-async",
|
"aws-smithy-async",
|
||||||
"aws-smithy-http",
|
"aws-smithy-http",
|
||||||
@ -401,9 +401,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-smithy-http"
|
name = "aws-smithy-http"
|
||||||
version = "0.55.3"
|
version = "0.55.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b3b693869133551f135e1f2c77cb0b8277d9e3e17feaf2213f735857c4f0d28"
|
checksum = "b60e2133beb9fe6ffe0b70deca57aaeff0a35ad24a9c6fab2fd3b4f45b99fdb5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-smithy-types",
|
"aws-smithy-types",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -423,9 +423,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-smithy-http-tower"
|
name = "aws-smithy-http-tower"
|
||||||
version = "0.55.3"
|
version = "0.55.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ae4f6c5798a247fac98a867698197d9ac22643596dc3777f0c76b91917616b9"
|
checksum = "3a4d94f556c86a0dd916a5d7c39747157ea8cb909ca469703e20fee33e448b67"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-smithy-http",
|
"aws-smithy-http",
|
||||||
"aws-smithy-types",
|
"aws-smithy-types",
|
||||||
@ -439,18 +439,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-smithy-json"
|
name = "aws-smithy-json"
|
||||||
version = "0.55.3"
|
version = "0.55.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "23f9f42fbfa96d095194a632fbac19f60077748eba536eb0b9fecc28659807f8"
|
checksum = "5ce3d6e6ebb00b2cce379f079ad5ec508f9bcc3a9510d9b9c1840ed1d6f8af39"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-smithy-types",
|
"aws-smithy-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-smithy-query"
|
name = "aws-smithy-query"
|
||||||
version = "0.55.3"
|
version = "0.55.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "98819eb0b04020a1c791903533b638534ae6c12e2aceda3e6e6fba015608d51d"
|
checksum = "d58edfca32ef9bfbc1ca394599e17ea329cb52d6a07359827be74235b64b3298"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-smithy-types",
|
"aws-smithy-types",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
@ -458,9 +458,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-smithy-types"
|
name = "aws-smithy-types"
|
||||||
version = "0.55.3"
|
version = "0.55.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "16a3d0bf4f324f4ef9793b86a1701d9700fbcdbd12a846da45eed104c634c6e8"
|
checksum = "58db46fc1f4f26be01ebdb821751b4e2482cd43aa2b64a0348fb89762defaffa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64-simd",
|
"base64-simd",
|
||||||
"itoa",
|
"itoa",
|
||||||
@ -471,18 +471,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-smithy-xml"
|
name = "aws-smithy-xml"
|
||||||
version = "0.55.3"
|
version = "0.55.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1b9d12875731bd07e767be7baad95700c3137b56730ec9ddeedb52a5e5ca63b"
|
checksum = "fb557fe4995bd9ec87fb244bbb254666a971dc902a783e9da8b7711610e9664c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"xmlparser",
|
"xmlparser",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-types"
|
name = "aws-types"
|
||||||
version = "0.55.3"
|
version = "0.55.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6dd209616cc8d7bfb82f87811a5c655dc97537f592689b18743bddf5dc5c4829"
|
checksum = "de0869598bfe46ec44ffe17e063ed33336e59df90356ca8ff0e8da6f7c1d994b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-credential-types",
|
"aws-credential-types",
|
||||||
"aws-smithy-async",
|
"aws-smithy-async",
|
||||||
@ -3372,9 +3372,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "postgrest"
|
name = "postgrest"
|
||||||
version = "1.6.0"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a966c650b47a064e7082170b4be74fca08c088d893244fc4b70123e3c1f3ee7"
|
checksum = "e66400cb23a379592bc8c8bdc9adda652eef4a969b74ab78454a8e8c11330c2b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
]
|
]
|
||||||
@ -4030,9 +4030,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-native-certs"
|
name = "rustls-native-certs"
|
||||||
version = "0.6.3"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
|
checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"openssl-probe",
|
"openssl-probe",
|
||||||
"rustls-pemfile",
|
"rustls-pemfile",
|
||||||
@ -4151,15 +4151,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.18"
|
version = "1.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.175"
|
version = "1.0.178"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b"
|
checksum = "60363bdd39a7be0266a520dab25fdc9241d2f987b08a01e01f0ec6d06a981348"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
@ -4177,9 +4177,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.175"
|
version = "1.0.178"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4"
|
checksum = "f28482318d6641454cb273da158647922d1be6b5a2fcc6165cd89ebdd7ed576b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -5085,9 +5085,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "urlencoding"
|
name = "urlencoding"
|
||||||
version = "2.1.3"
|
version = "2.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf-8"
|
name = "utf-8"
|
||||||
|
@ -100,23 +100,18 @@ impl FolderOperationHandler for DocumentFolderOperation {
|
|||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
let mut write_guard = workspace_view_builder.write().await;
|
let mut write_guard = workspace_view_builder.write().await;
|
||||||
|
|
||||||
// Create a parent view named "⭐️ Getting started". and a child view named "Read me".
|
// Create a view named "⭐️ Getting started" with built-in README data.
|
||||||
// Don't modify this code unless you know what you are doing.
|
// Don't modify this code unless you know what you are doing.
|
||||||
write_guard
|
write_guard
|
||||||
.with_view_builder(|view_builder| async {
|
.with_view_builder(|view_builder| async {
|
||||||
view_builder
|
let view = view_builder.with_name("⭐️ Getting started").build();
|
||||||
.with_name("⭐️ Getting started")
|
// create a empty document
|
||||||
.with_child_view_builder(|child_view_builder| async {
|
let json_str = include_str!("../../assets/read_me.json");
|
||||||
let view = child_view_builder.with_name("Read me").build();
|
let document_pb = JsonToDocumentParser::json_str_to_document(json_str).unwrap();
|
||||||
let json_str = include_str!("../../assets/read_me.json");
|
manager
|
||||||
let document_pb = JsonToDocumentParser::json_str_to_document(json_str).unwrap();
|
.create_document(&view.parent_view.id, Some(document_pb.into()))
|
||||||
manager
|
.unwrap();
|
||||||
.create_document(&view.parent_view.id, Some(document_pb.into()))
|
view
|
||||||
.unwrap();
|
|
||||||
view
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.build()
|
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -27,14 +27,7 @@ impl DefaultFolderBuilder {
|
|||||||
|
|
||||||
let views = workspace_view_builder.write().await.build();
|
let views = workspace_view_builder.write().await.build();
|
||||||
// Safe to unwrap because we have at least one view. check out the DocumentFolderOperation.
|
// Safe to unwrap because we have at least one view. check out the DocumentFolderOperation.
|
||||||
let first_view = views
|
let first_view = views.first().unwrap().parent_view.clone();
|
||||||
.first()
|
|
||||||
.unwrap()
|
|
||||||
.child_views
|
|
||||||
.first()
|
|
||||||
.unwrap()
|
|
||||||
.parent_view
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let first_level_views = views
|
let first_level_views = views
|
||||||
.iter()
|
.iter()
|
||||||
|
Loading…
Reference in New Issue
Block a user