diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 40f51c8132..ab1becf24f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -34,6 +34,7 @@ jobs: with: channel: 'stable' cache: true + flutter-version: '3.0.0' - name: Cache Cargo uses: actions/cache@v2 diff --git a/.github/workflows/dart_lint.yml b/.github/workflows/dart_lint.yml index 81460cec1c..ca0536906e 100644 --- a/.github/workflows/dart_lint.yml +++ b/.github/workflows/dart_lint.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v2 - uses: subosito/flutter-action@v1 with: - flutter-version: '2.10.0' + flutter-version: '3.0.0' channel: "stable" - name: Deps Flutter run: flutter packages pub get diff --git a/.github/workflows/dart_test.yml b/.github/workflows/dart_test.yml index db479d2905..9938f02091 100644 --- a/.github/workflows/dart_test.yml +++ b/.github/workflows/dart_test.yml @@ -25,6 +25,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: 'stable' + flutter-version: '3.0.0' cache: true - name: Cache Cargo diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 93dedaa43d..4d500c4659 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,6 +50,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: 'stable' + flutter-version: '3.0.0' - name: Pre build working-directory: frontend @@ -98,6 +99,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: 'stable' + flutter-version: '3.0.0' - name: Pre build working-directory: frontend diff --git a/frontend/Makefile.toml b/frontend/Makefile.toml index 7c82e17ba7..c46687a2df 100644 --- a/frontend/Makefile.toml +++ b/frontend/Makefile.toml @@ -7,6 +7,7 @@ extend = [ { path = "scripts/makefile/docker.toml" }, { path = "scripts/makefile/env.toml" }, { path = "scripts/makefile/flutter.toml" }, + { path = "scripts/makefile/tool.toml" }, ] [config] diff --git a/frontend/app_flowy/.vscode/launch.json b/frontend/app_flowy/.vscode/launch.json index b271a1e6a1..e5ea3cdf6f 100644 --- a/frontend/app_flowy/.vscode/launch.json +++ b/frontend/app_flowy/.vscode/launch.json @@ -5,18 +5,30 @@ "version": "0.2.0", "configurations": [ { - "name": "app_flowy", + // This task builds the Rust and Dart code of AppFlowy. + "name": "Build", "request": "launch", "program": "${workspaceRoot}/lib/main.dart", - "type": "dart", "preLaunchTask": "build_flowy_sdk", + "type": "dart", "env": { "RUST_LOG": "debug" }, "cwd": "${workspaceRoot}" }, { - "name": "app_flowy(trace)", + // This task only build the Dart code of AppFlowy. + "name": "Build (Dart)", + "request": "launch", + "program": "${workspaceRoot}/lib/main.dart", + "type": "dart", + "env": { + "RUST_LOG": "debug" + }, + "cwd": "${workspaceRoot}" + }, + { + "name": "Build (trace log)", "request": "launch", "program": "${workspaceRoot}/lib/main.dart", "type": "dart", @@ -27,7 +39,7 @@ "cwd": "${workspaceRoot}" }, { - "name": "app_flowy (profile mode)", + "name": "Build (profile mode)", "request": "launch", "type": "dart", "flutterMode": "profile" diff --git a/frontend/app_flowy/assets/images/grid/field/url.svg b/frontend/app_flowy/assets/images/grid/field/url.svg new file mode 100644 index 0000000000..f00f5c7aa2 --- /dev/null +++ b/frontend/app_flowy/assets/images/grid/field/url.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index 8755f05952..4e6c8f3420 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -160,6 +160,7 @@ "numberFieldName": "Numbers", "singleSelectFieldName": "Select", "multiSelectFieldName": "Multiselect", + "urlFieldName": "URL", "numberFormat": " Number format", "dateFormat": " Date format", "includeTime": " Include time", @@ -168,6 +169,7 @@ "dateFormatLocal": "Month/Month/Day", "dateFormatUS": "Month/Month/Day", "timeFormat": " Time format", + "invalidTimeFormat": "Invalid format", "timeFormatTwelveHour": "12 hour", "timeFormatTwentyFourHour": "24 hour", "addSelectOption": "Add an option", diff --git a/frontend/app_flowy/lib/core/frameless_window.dart b/frontend/app_flowy/lib/core/frameless_window.dart new file mode 100644 index 0000000000..a7d6417cd3 --- /dev/null +++ b/frontend/app_flowy/lib/core/frameless_window.dart @@ -0,0 +1,67 @@ +import 'package:flutter/services.dart'; +import 'package:flutter/material.dart'; +import 'dart:io' show Platform; + +class CocoaWindowChannel { + CocoaWindowChannel._(); + + final MethodChannel _channel = const MethodChannel("flutter/cocoaWindow"); + + static final CocoaWindowChannel instance = CocoaWindowChannel._(); + + Future setWindowPosition(Offset offset) async { + await _channel.invokeMethod("setWindowPosition", [offset.dx, offset.dy]); + } + + Future> getWindowPosition() async { + final raw = await _channel.invokeMethod("getWindowPosition"); + final arr = raw as List; + final List result = arr.map((s) => s as double).toList(); + return result; + } + + Future zoom() async { + await _channel.invokeMethod("zoom"); + } +} + +class MoveWindowDetector extends StatefulWidget { + const MoveWindowDetector({Key? key, this.child}) : super(key: key); + + final Widget? child; + + @override + _MoveWindowDetectorState createState() => _MoveWindowDetectorState(); +} + +class _MoveWindowDetectorState extends State { + double winX = 0; + double winY = 0; + + @override + Widget build(BuildContext context) { + if (!Platform.isMacOS) { + return widget.child ?? Container(); + } + return GestureDetector( + // https://stackoverflow.com/questions/52965799/flutter-gesturedetector-not-working-with-containers-in-stack + behavior: HitTestBehavior.translucent, + onDoubleTap: () async { + await CocoaWindowChannel.instance.zoom(); + }, + onPanStart: (DragStartDetails details) { + winX = details.globalPosition.dx; + winY = details.globalPosition.dy; + }, + onPanUpdate: (DragUpdateDetails details) async { + final windowPos = await CocoaWindowChannel.instance.getWindowPosition(); + final double dx = windowPos[0]; + final double dy = windowPos[1]; + final deltaX = details.globalPosition.dx - winX; + final deltaY = details.globalPosition.dy - winY; + await CocoaWindowChannel.instance.setWindowPosition(Offset(dx + deltaX, dy - deltaY)); + }, + child: widget.child, + ); + } +} diff --git a/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart b/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart index bad8000643..4a78815beb 100644 --- a/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart +++ b/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart @@ -88,8 +88,9 @@ class _SkipLogInScreenState extends State { } _launchURL(String url) async { - if (await canLaunch(url)) { - await launch(url); + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); } else { throw 'Could not launch $url'; } diff --git a/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart b/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart index 663e630a6a..e5eabe98e4 100644 --- a/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + import 'package:app_flowy/plugin/plugin.dart'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/app/app_listener.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart index 07be14efa2..68f8eada78 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart @@ -10,13 +10,13 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; - import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart'; import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; - +import 'dart:convert' show utf8; part 'cell_service.freezed.dart'; part 'data_loader.dart'; part 'context_builder.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart index ed191c7d60..e4141c3e16 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart @@ -1,8 +1,9 @@ part of 'cell_service.dart'; -typedef GridCellContext = _GridCellContext; +typedef GridCellContext = _GridCellContext; typedef GridSelectOptionCellContext = _GridCellContext; typedef GridDateCellContext = _GridCellContext; +typedef GridURLCellContext = _GridCellContext; class GridCellContextBuilder { final GridCellCache _cellCache; @@ -16,26 +17,33 @@ class GridCellContextBuilder { _GridCellContext build() { switch (_gridCell.field.fieldType) { case FieldType.Checkbox: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + parser: StringCellDataParser(), + ); return GridCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: GridCellDataLoader(gridCell: _gridCell), + cellDataLoader: cellDataLoader, cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); case FieldType.DateTime: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + parser: DateCellDataParser(), + ); + return GridDateCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: DateCellDataLoader(gridCell: _gridCell), + cellDataLoader: cellDataLoader, cellDataPersistence: DateCellDataPersistence(gridCell: _gridCell), ); case FieldType.Number: final cellDataLoader = GridCellDataLoader( gridCell: _gridCell, - config: const GridCellDataConfig( - reloadOnCellChanged: true, - reloadOnFieldChanged: true, - ), + parser: StringCellDataParser(), + config: const GridCellDataConfig(reloadOnCellChanged: true, reloadOnFieldChanged: true), ); return GridCellContext( gridCell: _gridCell, @@ -44,26 +52,49 @@ class GridCellContextBuilder { cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); case FieldType.RichText: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + parser: StringCellDataParser(), + ); return GridCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: GridCellDataLoader(gridCell: _gridCell), + cellDataLoader: cellDataLoader, cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); case FieldType.MultiSelect: case FieldType.SingleSelect: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + parser: SelectOptionCellDataParser(), + config: const GridCellDataConfig(reloadOnFieldChanged: true), + ); + return GridSelectOptionCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: SelectOptionCellDataLoader(gridCell: _gridCell), + cellDataLoader: cellDataLoader, + cellDataPersistence: CellDataPersistence(gridCell: _gridCell), + ); + + case FieldType.URL: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + parser: URLCellDataParser(), + ); + return GridURLCellContext( + gridCell: _gridCell, + cellCache: _cellCache, + cellDataLoader: cellDataLoader, cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); - default: - throw UnimplementedError; } + throw UnimplementedError; } } +// T: the type of the CellData +// D: the type of the data that will be save to disk // ignore: must_be_immutable class _GridCellContext extends Equatable { final GridCell gridCell; @@ -77,7 +108,8 @@ class _GridCellContext extends Equatable { late final ValueNotifier _cellDataNotifier; bool isListening = false; VoidCallback? _onFieldChangedFn; - Timer? _delayOperation; + Timer? _loadDataOperation; + Timer? _saveDataOperation; _GridCellContext({ required this.gridCell, @@ -107,7 +139,7 @@ class _GridCellContext extends Equatable { FieldType get fieldType => gridCell.field.fieldType; - VoidCallback? startListening({required void Function(T) onCellChanged}) { + VoidCallback? startListening({required void Function(T?) onCellChanged}) { if (isListening) { Log.error("Already started. It seems like you should call clone first"); return null; @@ -131,10 +163,7 @@ class _GridCellContext extends Equatable { } onCellChangedFn() { - final value = _cellDataNotifier.value; - if (value is T) { - onCellChanged(value); - } + onCellChanged(_cellDataNotifier.value); if (cellDataLoader.config.reloadOnCellChanged) { _loadData(); @@ -149,9 +178,9 @@ class _GridCellContext extends Equatable { _cellDataNotifier.removeListener(fn); } - T? getCellData() { + T? getCellData({bool loadIfNoCache = true}) { final data = cellCache.get(_cacheKey); - if (data == null) { + if (data == null && loadIfNoCache) { _loadData(); } return data; @@ -161,13 +190,26 @@ class _GridCellContext extends Equatable { return _fieldService.getFieldTypeOptionData(fieldType: fieldType); } - Future> saveCellData(D data) { - return cellDataPersistence.save(data); + void saveCellData(D data, {bool deduplicate = false, void Function(Option)? resultCallback}) async { + if (deduplicate) { + _loadDataOperation?.cancel(); + _loadDataOperation = Timer(const Duration(milliseconds: 300), () async { + final result = await cellDataPersistence.save(data); + if (resultCallback != null) { + resultCallback(result); + } + }); + } else { + final result = await cellDataPersistence.save(data); + if (resultCallback != null) { + resultCallback(result); + } + } } void _loadData() { - _delayOperation?.cancel(); - _delayOperation = Timer(const Duration(milliseconds: 10), () { + _loadDataOperation?.cancel(); + _loadDataOperation = Timer(const Duration(milliseconds: 10), () { cellDataLoader.loadData().then((data) { _cellDataNotifier.value = data; cellCache.insert(GridCellCacheData(key: _cacheKey, object: data)); @@ -176,7 +218,8 @@ class _GridCellContext extends Equatable { } void dispose() { - _delayOperation?.cancel(); + _loadDataOperation?.cancel(); + _saveDataOperation?.cancel(); if (_onFieldChangedFn != null) { cellCache.removeFieldListener(_cacheKey, _onFieldChangedFn!); diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart index 5be0435323..92caedc4e9 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart @@ -4,8 +4,8 @@ abstract class IGridCellDataConfig { // The cell data will reload if it receives the field's change notification. bool get reloadOnFieldChanged; - // The cell data will reload if it receives the cell's change notification. - // For example, the number cell should be reloaded after user input the number. + // When the reloadOnCellChanged is true, it will load the cell data after user input. + // For example: The number cell reload the cell data that carries the format // user input: 12 // cell display: $12 bool get reloadOnCellChanged; @@ -30,60 +30,49 @@ abstract class IGridCellDataLoader { IGridCellDataConfig get config; } -class GridCellDataLoader extends IGridCellDataLoader { +abstract class ICellDataParser { + T? parserData(List data); +} + +class GridCellDataLoader extends IGridCellDataLoader { final CellService service = CellService(); final GridCell gridCell; + final ICellDataParser parser; @override final IGridCellDataConfig config; GridCellDataLoader({ required this.gridCell, + required this.parser, this.config = const GridCellDataConfig(), }); @override - Future loadData() { + Future loadData() { final fut = service.getCell( gridId: gridCell.gridId, fieldId: gridCell.field.id, rowId: gridCell.rowId, ); - return fut.then((result) { - return result.fold((data) => data, (err) { + return fut.then( + (result) => result.fold((Cell cell) { + try { + if (cell.data.isEmpty) { + return null; + } else { + return parser.parserData(cell.data); + } + } catch (e, s) { + Log.error('$parser parser cellData failed, $e'); + Log.error('Stack trace \n $s'); + return null; + } + }, (err) { Log.error(err); return null; - }); - }); - } -} - -class DateCellDataLoader extends IGridCellDataLoader { - final GridCell gridCell; - final IGridCellDataConfig _config; - DateCellDataLoader({ - required this.gridCell, - }) : _config = const GridCellDataConfig(reloadOnFieldChanged: true); - - @override - IGridCellDataConfig get config => _config; - - @override - Future loadData() { - final payload = CellIdentifierPayload.create() - ..gridId = gridCell.gridId - ..fieldId = gridCell.field.id - ..rowId = gridCell.rowId; - - return GridEventGetDateCellData(payload).send().then((result) { - return result.fold( - (data) => data, - (err) { - Log.error(err); - return null; - }, - ); - }); + }), + ); } } @@ -109,3 +98,31 @@ class SelectOptionCellDataLoader extends IGridCellDataLoader const GridCellDataConfig(reloadOnFieldChanged: true); } + +class StringCellDataParser implements ICellDataParser { + @override + String? parserData(List data) { + return utf8.decode(data); + } +} + +class DateCellDataParser implements ICellDataParser { + @override + DateCellData? parserData(List data) { + return DateCellData.fromBuffer(data); + } +} + +class SelectOptionCellDataParser implements ICellDataParser { + @override + SelectOptionCellData? parserData(List data) { + return SelectOptionCellData.fromBuffer(data); + } +} + +class URLCellDataParser implements ICellDataParser { + @override + URLCellData? parserData(List data) { + return URLCellData.fromBuffer(data); + } +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart index cecfaa2f04..b8e2b13bbc 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart @@ -1,4 +1,3 @@ -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -16,15 +15,15 @@ class CheckboxCellBloc extends Bloc { }) : super(CheckboxCellState.initial(cellContext)) { on( (event, emit) async { - await event.map( - initial: (_Initial value) { + await event.when( + initial: () { _startListening(); }, - select: (_Selected value) async { + select: () async { _updateCellData(); }, - didReceiveCellUpdate: (_DidReceiveCellUpdate value) { - emit(state.copyWith(isSelected: _isSelected(value.cell))); + didReceiveCellUpdate: (cellData) { + emit(state.copyWith(isSelected: _isSelected(cellData))); }, ); }, @@ -43,9 +42,9 @@ class CheckboxCellBloc extends Bloc { } void _startListening() { - _onCellChangedFn = cellContext.startListening(onCellChanged: ((cell) { + _onCellChangedFn = cellContext.startListening(onCellChanged: ((cellData) { if (!isClosed) { - add(CheckboxCellEvent.didReceiveCellUpdate(cell)); + add(CheckboxCellEvent.didReceiveCellUpdate(cellData)); } })); } @@ -59,7 +58,7 @@ class CheckboxCellBloc extends Bloc { class CheckboxCellEvent with _$CheckboxCellEvent { const factory CheckboxCellEvent.initial() = _Initial; const factory CheckboxCellEvent.select() = _Selected; - const factory CheckboxCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate; + const factory CheckboxCellEvent.didReceiveCellUpdate(String? cellData) = _DidReceiveCellUpdate; } @freezed @@ -73,7 +72,6 @@ class CheckboxCellState with _$CheckboxCellState { } } -bool _isSelected(Cell? cell) { - final content = cell?.content ?? ""; - return content == "Yes"; +bool _isSelected(String? cellData) { + return cellData == "Yes"; } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart index a0ba35f9d6..15f18707f8 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart @@ -1,6 +1,9 @@ +import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; +import 'package:easy_localization/easy_localization.dart' show StringTranslateExtension; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -34,7 +37,7 @@ class DateCalBloc extends Bloc { setFocusedDay: (focusedDay) { emit(state.copyWith(focusedDay: focusedDay)); }, - didReceiveCellUpdate: (DateCellData cellData) { + didReceiveCellUpdate: (DateCellData? cellData) { final dateData = dateDataFromCellData(cellData); final time = dateData.foldRight("", (dateData, previous) => dateData.time); emit(state.copyWith(dateData: dateData, time: time)); @@ -80,25 +83,41 @@ class DateCalBloc extends Bloc { return; } - final result = await cellContext.saveCellData(newDateData); - result.fold( - () => emit(state.copyWith( - dateData: Some(newDateData), - timeFormatError: none(), - )), - (err) { - switch (ErrorCode.valueOf(err.code)!) { - case ErrorCode.InvalidDateTimeFormat: - emit(state.copyWith( - dateData: Some(newDateData), - timeFormatError: Some(err.toString()), - )); - break; - default: - Log.error(err); - } - }, - ); + cellContext.saveCellData(newDateData, resultCallback: (result) { + result.fold( + () => emit(state.copyWith( + dateData: Some(newDateData), + timeFormatError: none(), + )), + (err) { + switch (ErrorCode.valueOf(err.code)!) { + case ErrorCode.InvalidDateTimeFormat: + emit(state.copyWith( + dateData: Some(newDateData), + timeFormatError: Some(timeFormatPrompt(err)), + )); + break; + default: + Log.error(err); + } + }, + ); + }); + } + + String timeFormatPrompt(FlowyError error) { + String msg = LocaleKeys.grid_field_invalidTimeFormat.tr() + ". "; + switch (state.dateTypeOption.timeFormat) { + case TimeFormat.TwelveHour: + msg = msg + "e.g. 01: 00 AM"; + break; + case TimeFormat.TwentyFourHour: + msg = msg + "e.g. 13: 00"; + break; + default: + break; + } + return msg; } @override @@ -165,7 +184,7 @@ class DateCalEvent with _$DateCalEvent { const factory DateCalEvent.setDateFormat(DateFormat dateFormat) = _DateFormat; const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime; const factory DateCalEvent.setTime(String time) = _Time; - const factory DateCalEvent.didReceiveCellUpdate(DateCellData data) = _DidReceiveCellUpdate; + const factory DateCalEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate; } @freezed diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart index 4b068dd289..b06a3d60b3 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart @@ -16,7 +16,13 @@ class DateCellBloc extends Bloc { (event, emit) async { event.when( initial: () => _startListening(), - didReceiveCellUpdate: (DateCellData value) => emit(state.copyWith(data: Some(value))), + didReceiveCellUpdate: (DateCellData? cellData) { + if (cellData != null) { + emit(state.copyWith(data: Some(cellData))); + } else { + emit(state.copyWith(data: none())); + } + }, didReceiveFieldUpdate: (Field value) => emit(state.copyWith(field: value)), ); }, @@ -47,7 +53,7 @@ class DateCellBloc extends Bloc { @freezed class DateCellEvent with _$DateCellEvent { const factory DateCellEvent.initial() = _InitialCell; - const factory DateCellEvent.didReceiveCellUpdate(DateCellData data) = _DidReceiveCellUpdate; + const factory DateCellEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate; const factory DateCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate; } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart index 217ae4d384..8157f6a3f2 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart @@ -1,4 +1,3 @@ -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -20,7 +19,7 @@ class NumberCellBloc extends Bloc { _startListening(); }, didReceiveCellUpdate: (_DidReceiveCellUpdate value) { - emit(state.copyWith(content: value.cell.content)); + emit(state.copyWith(content: value.cellContent ?? "")); }, updateCell: (_UpdateCell value) async { await _updateCellValue(value, emit); @@ -46,9 +45,9 @@ class NumberCellBloc extends Bloc { void _startListening() { _onCellChangedFn = cellContext.startListening( - onCellChanged: ((cell) { + onCellChanged: ((cellContent) { if (!isClosed) { - add(NumberCellEvent.didReceiveCellUpdate(cell)); + add(NumberCellEvent.didReceiveCellUpdate(cellContent)); } }), ); @@ -59,7 +58,7 @@ class NumberCellBloc extends Bloc { class NumberCellEvent with _$NumberCellEvent { const factory NumberCellEvent.initial() = _Initial; const factory NumberCellEvent.updateCell(String text) = _UpdateCell; - const factory NumberCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate; + const factory NumberCellEvent.didReceiveCellUpdate(String? cellContent) = _DidReceiveCellUpdate; } @freezed @@ -69,7 +68,7 @@ class NumberCellState with _$NumberCellState { }) = _NumberCellState; factory NumberCellState.initial(GridCellContext context) { - final cell = context.getCellData(); - return NumberCellState(content: cell?.content ?? ""); + final cellContent = context.getCellData() ?? ""; + return NumberCellState(content: cellContent); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart index 056b65e556..c6393e4831 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart @@ -21,7 +21,6 @@ class SelectOptionCellBloc extends Bloc options, List selectedOptions, ) = _DidReceiveOptions; } @@ -66,7 +63,6 @@ class SelectOptionCellEvent with _$SelectOptionCellEvent { @freezed class SelectOptionCellState with _$SelectOptionCellState { const factory SelectOptionCellState({ - required List options, required List selectedOptions, }) = _SelectOptionCellState; @@ -74,7 +70,6 @@ class SelectOptionCellState with _$SelectOptionCellState { final data = context.getCellData(); return SelectOptionCellState( - options: data?.options ?? [], selectedOptions: data?.selectOptions ?? [], ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart index 2702e6006f..87eabdf759 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; @@ -6,23 +7,28 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; import 'select_option_service.dart'; +import 'package:collection/collection.dart'; part 'select_option_editor_bloc.freezed.dart'; class SelectOptionCellEditorBloc extends Bloc { final SelectOptionService _selectOptionService; final GridSelectOptionCellContext cellContext; + late final GridFieldsListener _fieldListener; void Function()? _onCellChangedFn; + Timer? _delayOperation; SelectOptionCellEditorBloc({ required this.cellContext, }) : _selectOptionService = SelectOptionService(gridCell: cellContext.gridCell), + _fieldListener = GridFieldsListener(gridId: cellContext.gridId), super(SelectOptionEditorState.initial(cellContext)) { on( (event, emit) async { await event.map( initial: (_Initial value) async { _startListening(); + _loadOptions(); }, didReceiveOptions: (_DidReceiveOptions value) { final result = _makeOptions(state.filter, value.options); @@ -62,6 +68,8 @@ class SelectOptionCellEditorBloc extends Bloc add(SelectOptionEditorEvent.didReceiveOptions(data.options, data.selectOptions)), + (err) { + Log.error(err); + return null; + }, + ); + }); + }); + } + _MakeOptionResult _makeOptions(Option filter, List allOptions) { final List options = List.from(allOptions); Option createOption = filter; @@ -134,13 +160,21 @@ class SelectOptionCellEditorBloc extends Bloc Log.error(err), + ); + }); } } @@ -167,7 +201,7 @@ class SelectOptionEditorState with _$SelectOptionEditorState { }) = _SelectOptionEditorState; factory SelectOptionEditorState.initial(GridSelectOptionCellContext context) { - final data = context.getCellData(); + final data = context.getCellData(loadIfNoCache: false); return SelectOptionEditorState( options: data?.options ?? [], allOptions: data?.options ?? [], diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart index 0b8b8be5e0..e3b7fd2dca 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart @@ -1,4 +1,3 @@ -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -14,21 +13,16 @@ class TextCellBloc extends Bloc { }) : super(TextCellState.initial(cellContext)) { on( (event, emit) async { - await event.map( - initial: (_InitialCell value) async { + await event.when( + initial: () async { _startListening(); }, - updateText: (_UpdateText value) { - cellContext.saveCellData(value.text); - emit(state.copyWith(content: value.text)); + updateText: (text) { + cellContext.saveCellData(text); + emit(state.copyWith(content: text)); }, - didReceiveCellData: (_DidReceiveCellData value) { - emit(state.copyWith(content: value.cellData.cell?.content ?? "")); - }, - didReceiveCellUpdate: (_DidReceiveCellUpdate value) { - emit(state.copyWith( - content: value.cell.content, - )); + didReceiveCellUpdate: (content) { + emit(state.copyWith(content: content)); }, ); }, @@ -47,9 +41,9 @@ class TextCellBloc extends Bloc { void _startListening() { _onCellChangedFn = cellContext.startListening( - onCellChanged: ((cell) { + onCellChanged: ((cellContent) { if (!isClosed) { - add(TextCellEvent.didReceiveCellUpdate(cell)); + add(TextCellEvent.didReceiveCellUpdate(cellContent ?? "")); } }), ); @@ -59,8 +53,7 @@ class TextCellBloc extends Bloc { @freezed class TextCellEvent with _$TextCellEvent { const factory TextCellEvent.initial() = _InitialCell; - const factory TextCellEvent.didReceiveCellData(GridCell cellData) = _DidReceiveCellData; - const factory TextCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate; + const factory TextCellEvent.didReceiveCellUpdate(String cellContent) = _DidReceiveCellUpdate; const factory TextCellEvent.updateText(String text) = _UpdateText; } @@ -71,6 +64,6 @@ class TextCellState with _$TextCellState { }) = _TextCellState; factory TextCellState.initial(GridCellContext context) => TextCellState( - content: context.getCellData()?.content ?? "", + content: context.getCellData() ?? "", ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart new file mode 100644 index 0000000000..609c625001 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart @@ -0,0 +1,73 @@ +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'dart:async'; +import 'cell_service/cell_service.dart'; + +part 'url_cell_bloc.freezed.dart'; + +class URLCellBloc extends Bloc { + final GridURLCellContext cellContext; + void Function()? _onCellChangedFn; + URLCellBloc({ + required this.cellContext, + }) : super(URLCellState.initial(cellContext)) { + on( + (event, emit) async { + event.when( + initial: () { + _startListening(); + }, + didReceiveCellUpdate: (cellData) { + emit(state.copyWith( + content: cellData?.content ?? "", + url: cellData?.url ?? "", + )); + }, + ); + }, + ); + } + + @override + Future close() async { + if (_onCellChangedFn != null) { + cellContext.removeListener(_onCellChangedFn!); + _onCellChangedFn = null; + } + cellContext.dispose(); + return super.close(); + } + + void _startListening() { + _onCellChangedFn = cellContext.startListening( + onCellChanged: ((cellData) { + if (!isClosed) { + add(URLCellEvent.didReceiveCellUpdate(cellData)); + } + }), + ); + } +} + +@freezed +class URLCellEvent with _$URLCellEvent { + const factory URLCellEvent.initial() = _InitialCell; + const factory URLCellEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate; +} + +@freezed +class URLCellState with _$URLCellState { + const factory URLCellState({ + required String content, + required String url, + }) = _URLCellState; + + factory URLCellState.initial(GridURLCellContext context) { + final cellData = context.getCellData(); + return URLCellState( + content: cellData?.content ?? "", + url: cellData?.url ?? "", + ); + } +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_editor_bloc.dart new file mode 100644 index 0000000000..6e4990943f --- /dev/null +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_editor_bloc.dart @@ -0,0 +1,73 @@ +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'dart:async'; +import 'cell_service/cell_service.dart'; + +part 'url_cell_editor_bloc.freezed.dart'; + +class URLCellEditorBloc extends Bloc { + final GridURLCellContext cellContext; + void Function()? _onCellChangedFn; + URLCellEditorBloc({ + required this.cellContext, + }) : super(URLCellEditorState.initial(cellContext)) { + on( + (event, emit) async { + event.when( + initial: () { + _startListening(); + }, + updateText: (text) { + cellContext.saveCellData(text, deduplicate: true); + emit(state.copyWith(content: text)); + }, + didReceiveCellUpdate: (cellData) { + emit(state.copyWith(content: cellData?.content ?? "")); + }, + ); + }, + ); + } + + @override + Future close() async { + if (_onCellChangedFn != null) { + cellContext.removeListener(_onCellChangedFn!); + _onCellChangedFn = null; + } + cellContext.dispose(); + return super.close(); + } + + void _startListening() { + _onCellChangedFn = cellContext.startListening( + onCellChanged: ((cellData) { + if (!isClosed) { + add(URLCellEditorEvent.didReceiveCellUpdate(cellData)); + } + }), + ); + } +} + +@freezed +class URLCellEditorEvent with _$URLCellEditorEvent { + const factory URLCellEditorEvent.initial() = _InitialCell; + const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate; + const factory URLCellEditorEvent.updateText(String text) = _UpdateText; +} + +@freezed +class URLCellEditorState with _$URLCellEditorState { + const factory URLCellEditorState({ + required String content, + }) = _URLCellEditorState; + + factory URLCellEditorState.initial(GridURLCellContext context) { + final cellData = context.getCellData(); + return URLCellEditorState( + content: cellData?.content ?? "", + ); + } +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart index 4ed54c5f3b..38ebfc63fc 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; @@ -6,8 +8,6 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - import 'cell/cell_service/cell_service.dart'; import 'row/row_service.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart b/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart index 796a0357b9..f3d9930842 100644 --- a/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart @@ -49,6 +49,9 @@ class HomeBloc extends Bloc { unauthorized: (_Unauthorized value) { emit(state.copyWith(unauthorized: true)); }, + collapseMenu: (e) { + emit(state.copyWith(isMenuCollapsed: !state.isMenuCollapsed)); + }, ); }); } @@ -77,6 +80,7 @@ class HomeEvent with _$HomeEvent { const factory HomeEvent.dismissEditPannel() = _DismissEditPannel; const factory HomeEvent.didReceiveWorkspaceSetting(CurrentWorkspaceSetting setting) = _DidReceiveWorkspaceSetting; const factory HomeEvent.unauthorized(String msg) = _Unauthorized; + const factory HomeEvent.collapseMenu() = _CollapseMenu; } @freezed @@ -87,6 +91,7 @@ class HomeState with _$HomeState { required Option pannelContext, required CurrentWorkspaceSetting workspaceSetting, required bool unauthorized, + required bool isMenuCollapsed, }) = _HomeState; factory HomeState.initial(CurrentWorkspaceSetting workspaceSetting) => HomeState( @@ -95,5 +100,6 @@ class HomeState with _$HomeState { pannelContext: none(), workspaceSetting: workspaceSetting, unauthorized: false, + isMenuCollapsed: false, ); } diff --git a/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart b/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart index a2c167cde4..db8f2c534b 100644 --- a/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart @@ -25,10 +25,6 @@ class MenuBloc extends Bloc { listener.start(addAppCallback: _handleAppsOrFail); await _fetchApps(emit); }, - collapse: (e) async { - final isCollapse = state.isCollapse; - emit(state.copyWith(isCollapse: !isCollapse)); - }, openPage: (e) async { emit(state.copyWith(plugin: e.plugin)); }, @@ -94,7 +90,6 @@ class MenuBloc extends Bloc { @freezed class MenuEvent with _$MenuEvent { const factory MenuEvent.initial() = _Initial; - const factory MenuEvent.collapse() = _Collapse; const factory MenuEvent.openPage(Plugin plugin) = _OpenPage; const factory MenuEvent.createApp(String name, {String? desc}) = _CreateApp; const factory MenuEvent.moveApp(int fromIndex, int toIndex) = _MoveApp; @@ -104,14 +99,12 @@ class MenuEvent with _$MenuEvent { @freezed class MenuState with _$MenuState { const factory MenuState({ - required bool isCollapse, required List apps, required Either successOrFailure, required Plugin plugin, }) = _MenuState; factory MenuState.initial() => MenuState( - isCollapse: false, apps: [], successOrFailure: left(unit), plugin: makePlugin(pluginType: DefaultPlugin.blank.type()), diff --git a/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart b/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart index 65e315f56d..c16c965a82 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart @@ -1,8 +1,12 @@ +import 'dart:io' show Platform; + import 'package:app_flowy/startup/startup.dart'; +import 'package:app_flowy/workspace/application/home/home_bloc.dart'; import 'package:app_flowy/workspace/presentation/home/home_screen.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; import 'package:time/time.dart'; import 'package:fluttertoast/fluttertoast.dart'; @@ -11,6 +15,7 @@ import 'package:app_flowy/plugin/plugin.dart'; import 'package:app_flowy/workspace/presentation/plugins/blank/blank.dart'; import 'package:app_flowy/workspace/presentation/home/home_sizes.dart'; import 'package:app_flowy/workspace/presentation/home/navigation.dart'; +import 'package:app_flowy/core/frameless_window.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/style_widget/extension.dart'; import 'package:flowy_infra/notifier.dart'; @@ -152,7 +157,7 @@ class HomeStackManager { child: Selector( selector: (context, notifier) => notifier.titleWidget, builder: (context, widget, child) { - return const HomeTopBar(); + return const MoveWindowDetector(child: HomeTopBar()); }, ), ); @@ -191,6 +196,14 @@ class HomeTopBar extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ + BlocBuilder( + buildWhen: ((previous, current) => previous.isMenuCollapsed != current.isMenuCollapsed), + builder: (context, state) { + if (state.isMenuCollapsed && Platform.isMacOS) { + return const HSpace(80); + } + return const HSpace(0); + }), const FlowyNavigation(), const HSpace(16), ChangeNotifierProvider.value( diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart index d9fd618e9a..d352db6620 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart @@ -26,7 +26,7 @@ class ViewSection extends StatelessWidget { listenWhen: (p, c) => p.selectedView != c.selectedView, listener: (context, state) { if (state.selectedView != null) { - WidgetsBinding.instance?.addPostFrameCallback((_) { + WidgetsBinding.instance.addPostFrameCallback((_) { getIt().setPlugin(state.selectedView!.plugin()); }); } diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart index b888ab7631..0eb22e3d1f 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart @@ -1,6 +1,8 @@ export './app/header/header.dart'; export './app/menu_app.dart'; +import 'dart:io' show Platform; +import 'package:app_flowy/workspace/presentation/home/home_sizes.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:app_flowy/workspace/presentation/plugins/trash/menu.dart'; import 'package:flowy_infra/notifier.dart'; @@ -18,7 +20,9 @@ import 'package:expandable/expandable.dart'; import 'package:flowy_infra/time/duration.dart'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/menu/menu_bloc.dart'; -import 'package:app_flowy/workspace/presentation/home/home_sizes.dart'; +import 'package:app_flowy/workspace/application/home/home_bloc.dart'; +import 'package:app_flowy/core/frameless_window.dart'; +// import 'package:app_flowy/workspace/presentation/home/home_sizes.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; @@ -59,10 +63,10 @@ class HomeMenu extends StatelessWidget { getIt().setPlugin(state.plugin); }, ), - BlocListener( - listenWhen: (p, c) => p.isCollapse != c.isCollapse, + BlocListener( + listenWhen: (p, c) => p.isMenuCollapsed != c.isMenuCollapsed, listener: (context, state) { - _collapsedNotifier.value = state.isCollapse; + _collapsedNotifier.value = state.isMenuCollapsed; }, ) ], @@ -179,6 +183,17 @@ class MenuSharedState { class MenuTopBar extends StatelessWidget { const MenuTopBar({Key? key}) : super(key: key); + + Widget renderIcon(BuildContext context) { + if (Platform.isMacOS) { + return Container(); + } + final theme = context.watch(); + return (theme.isDark + ? svgWithSize("flowy_logo_dark_mode", const Size(92, 17)) + : svgWithSize("flowy_logo_with_text", const Size(92, 17))); + } + @override Widget build(BuildContext context) { final theme = context.watch(); @@ -186,20 +201,19 @@ class MenuTopBar extends StatelessWidget { builder: (context, state) { return SizedBox( height: HomeSizes.topBarHeight, - child: Row( + child: MoveWindowDetector( + child: Row( children: [ - (theme.isDark - ? svgWithSize("flowy_logo_dark_mode", const Size(92, 17)) - : svgWithSize("flowy_logo_with_text", const Size(92, 17))), + renderIcon(context), const Spacer(), FlowyIconButton( width: 28, - onPressed: () => context.read().add(const MenuEvent.collapse()), + onPressed: () => context.read().add(const HomeEvent.collapseMenu()), iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), icon: svgWidget("home/hide_menu", color: theme.iconColor), ) ], - ), + )), ); }, ); diff --git a/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart b/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart index b6947c79fe..f52b7224f6 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart @@ -1,3 +1,4 @@ +import 'package:app_flowy/workspace/application/home/home_bloc.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/notifier.dart'; @@ -95,6 +96,7 @@ class FlowyNavigation extends StatelessWidget { width: 24, onPressed: () { notifier.value = false; + ctx.read().add(const HomeEvent.collapseMenu()); }, iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2), icon: svgWidget("home/hide_menu", color: theme.iconColor), diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/tool_bar.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/tool_bar.dart index 2ab0b78cc9..8dae41f986 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/tool_bar.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/tool_bar.dart @@ -184,7 +184,7 @@ class _ToolbarButtonListState extends State with WidgetsBindi // Listening to the WidgetsBinding instance is necessary so that we can // hide the arrows when the window gets a new size and thus the toolbar // becomes scrollable/unscrollable. - WidgetsBinding.instance!.addObserver(this); + WidgetsBinding.instance.addObserver(this); // Workaround to allow the scroll controller attach to our ListView so that // we can detect if overflow arrows need to be shown on init. @@ -226,7 +226,7 @@ class _ToolbarButtonListState extends State with WidgetsBindi @override void dispose() { _controller.dispose(); - WidgetsBinding.instance!.removeObserver(this); + WidgetsBinding.instance.removeObserver(this); super.dispose(); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart index eab2903857..f8189e7f02 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart @@ -13,6 +13,7 @@ import 'date_cell/date_cell.dart'; import 'number_cell.dart'; import 'select_option_cell/select_option_cell.dart'; import 'text_cell.dart'; +import 'url_cell/url_cell.dart'; GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) { final key = ValueKey(gridCell.cellId()); @@ -32,10 +33,10 @@ GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, { return NumberCell(cellContextBuilder: cellContextBuilder, key: key); case FieldType.RichText: return GridTextCell(cellContextBuilder: cellContextBuilder, style: style, key: key); - - default: - throw UnimplementedError; + case FieldType.URL: + return GridURLCell(cellContextBuilder: cellContextBuilder, style: style, key: key); } + throw UnimplementedError; } class BlankCell extends StatelessWidget { @@ -47,13 +48,11 @@ class BlankCell extends StatelessWidget { } } -abstract class GridCellWidget extends HoverWidget { +abstract class GridCellWidget implements FlowyHoverWidget { @override final ValueNotifier onFocus = ValueNotifier(false); final GridCellRequestFocusNotifier requestFocus = GridCellRequestFocusNotifier(); - - GridCellWidget({Key? key}) : super(key: key); } class GridCellRequestFocusNotifier extends ChangeNotifier { @@ -151,7 +150,7 @@ class CellContainer extends StatelessWidget { }); if (expander != null) { - container = _CellEnterRegion(child: container, expander: expander!); + container = CellEnterRegion(child: container, expander: expander!); } return GestureDetector( @@ -181,10 +180,10 @@ class CellContainer extends StatelessWidget { } } -class _CellEnterRegion extends StatelessWidget { +class CellEnterRegion extends StatelessWidget { final Widget child; final Widget expander; - const _CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key); + const CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart index c4da2a223f..b2493d55ed 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart @@ -6,7 +6,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'cell_builder.dart'; -class CheckboxCell extends GridCellWidget { +class CheckboxCell extends StatefulWidget with GridCellWidget { final GridCellContextBuilder cellContextBuilder; CheckboxCell({ required this.cellContextBuilder, @@ -41,7 +41,7 @@ class _CheckboxCellState extends State { onPressed: () => context.read().add(const CheckboxCellEvent.select()), iconPadding: EdgeInsets.zero, icon: icon, - width: 23, + width: 20, ), ); }, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart index 5f0ccbc834..417c9f270e 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart @@ -18,7 +18,7 @@ abstract class GridCellDelegate { GridCellDelegate get delegate; } -class DateCell extends GridCellWidget { +class DateCell extends StatefulWidget with GridCellWidget { final GridCellContextBuilder cellContextBuilder; late final DateCellStyle? cellStyle; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart index c0b3427e65..dc694f48f9 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart @@ -7,7 +7,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'cell_builder.dart'; -class NumberCell extends GridCellWidget { +class NumberCell extends StatefulWidget with GridCellWidget { final GridCellContextBuilder cellContextBuilder; NumberCell({ diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart index 3cbf2331fd..6f8212106a 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart @@ -87,7 +87,7 @@ class SelectOptionTag extends StatelessWidget { Widget build(BuildContext context) { return ChoiceChip( pressElevation: 1, - label: FlowyText.medium(name, fontSize: 12), + label: FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis), selectedColor: color, backgroundColor: color, labelPadding: const EdgeInsets.symmetric(horizontal: 6), @@ -130,11 +130,18 @@ class SelectOptionTagCell extends StatelessWidget { child: InkWell( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 3), - child: Row(children: [ - SelectOptionTag.fromSelectOption(context: context, option: option), - const Spacer(), - ...children, - ]), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + fit: FlexFit.loose, + flex: 2, + child: SelectOptionTag.fromSelectOption(context: context, option: option), + ), + const Spacer(), + ...children, + ], + ), ), onTap: () => onSelected(option), ), diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart index 12415c816d..c57a1865be 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart @@ -20,7 +20,7 @@ class SelectOptionCellStyle extends GridCellStyle { }); } -class SingleSelectCell extends GridCellWidget { +class SingleSelectCell extends StatefulWidget with GridCellWidget { final GridCellContextBuilder cellContextBuilder; late final SelectOptionCellStyle? cellStyle; @@ -74,7 +74,7 @@ class _SingleSelectCellState extends State { } //---------------------------------------------------------------- -class MultiSelectCell extends GridCellWidget { +class MultiSelectCell extends StatefulWidget with GridCellWidget { final GridCellContextBuilder cellContextBuilder; late final SelectOptionCellStyle? cellStyle; @@ -160,7 +160,7 @@ class _SelectOptionCell extends StatelessWidget { .toList(); child = Align( alignment: Alignment.centerLeft, - child: Wrap(children: tags, spacing: 4, runSpacing: 4), + child: Wrap(children: tags, spacing: 4, runSpacing: 2), ); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart index f82d52cfb4..01972eb41a 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart @@ -225,7 +225,18 @@ class _SelectOptionCell extends StatelessWidget { height: GridSize.typeOptionItemHeight, child: Row( children: [ - Expanded(child: _body(theme, context)), + Flexible( + fit: FlexFit.loose, + child: SelectOptionTagCell( + option: option, + onSelected: (option) { + context.read().add(SelectOptionEditorEvent.selectOption(option.id)); + }, + children: [ + if (isSelected) svgWidget("grid/checkmark"), + ], + ), + ), FlowyIconButton( width: 30, onPressed: () => _showEditPannel(context), @@ -237,18 +248,6 @@ class _SelectOptionCell extends StatelessWidget { ); } - Widget _body(AppTheme theme, BuildContext context) { - return SelectOptionTagCell( - option: option, - onSelected: (option) { - context.read().add(SelectOptionEditorEvent.selectOption(option.id)); - }, - children: [ - if (isSelected) svgWidget("grid/checkmark"), - ], - ); - } - void _showEditPannel(BuildContext context) { final pannel = SelectOptionTypeOptionEditor( option: option, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart index 14ace02d28..1563d41d86 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart @@ -13,7 +13,7 @@ class GridTextCellStyle extends GridCellStyle { }); } -class GridTextCell extends GridCellWidget { +class GridTextCell extends StatefulWidget with GridCellWidget { final GridCellContextBuilder cellContextBuilder; late final GridTextCellStyle? cellStyle; GridTextCell({ diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart new file mode 100644 index 0000000000..055a4947c8 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart @@ -0,0 +1,96 @@ +import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; +import 'package:app_flowy/workspace/application/grid/cell/url_cell_editor_bloc.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter_bloc/flutter_bloc.dart'; + +class URLCellEditor extends StatefulWidget { + final GridURLCellContext cellContext; + const URLCellEditor({required this.cellContext, Key? key}) : super(key: key); + + @override + State createState() => _URLCellEditorState(); + + static void show( + BuildContext context, + GridURLCellContext cellContext, + ) { + FlowyOverlay.of(context).remove(identifier()); + final editor = URLCellEditor( + cellContext: cellContext, + ); + + // + FlowyOverlay.of(context).insertWithAnchor( + widget: OverlayContainer( + child: SizedBox(width: 200, child: editor), + constraints: BoxConstraints.loose(const Size(300, 160)), + ), + identifier: URLCellEditor.identifier(), + anchorContext: context, + anchorDirection: AnchorDirection.bottomWithCenterAligned, + ); + } + + static String identifier() { + return (URLCellEditor).toString(); + } +} + +class _URLCellEditorState extends State { + late URLCellEditorBloc _cellBloc; + late TextEditingController _controller; + + @override + void initState() { + _cellBloc = URLCellEditorBloc(cellContext: widget.cellContext); + _cellBloc.add(const URLCellEditorEvent.initial()); + _controller = TextEditingController(text: _cellBloc.state.content); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider.value( + value: _cellBloc, + child: BlocListener( + listener: (context, state) { + if (_controller.text != state.content) { + _controller.text = state.content; + } + }, + child: TextField( + autofocus: true, + controller: _controller, + onChanged: (value) => focusChanged(), + maxLines: null, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + decoration: const InputDecoration( + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + hintText: "", + isDense: true, + ), + ), + ), + ); + } + + @override + Future dispose() async { + _cellBloc.close(); + + super.dispose(); + } + + Future focusChanged() async { + if (mounted) { + if (_cellBloc.isClosed == false && _controller.text != _cellBloc.state.content) { + _cellBloc.add(URLCellEditorEvent.updateText(_controller.text)); + } + } + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart new file mode 100644 index 0000000000..db0dcade79 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart @@ -0,0 +1,131 @@ +import 'dart:async'; +import 'package:app_flowy/workspace/application/grid/cell/url_cell_bloc.dart'; +import 'package:flowy_infra/image.dart'; +import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra_ui/style_widget/icon_button.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:app_flowy/workspace/application/grid/prelude.dart'; +import 'package:url_launcher/url_launcher.dart'; +import '../cell_builder.dart'; +import 'cell_editor.dart'; + +class GridURLCellStyle extends GridCellStyle { + String? placeholder; + + GridURLCellStyle({ + this.placeholder, + }); +} + +class GridURLCell extends StatefulWidget with GridCellWidget { + final GridCellContextBuilder cellContextBuilder; + late final GridURLCellStyle? cellStyle; + GridURLCell({ + required this.cellContextBuilder, + GridCellStyle? style, + Key? key, + }) : super(key: key) { + if (style != null) { + cellStyle = (style as GridURLCellStyle); + } else { + cellStyle = null; + } + } + + @override + State createState() => _GridURLCellState(); +} + +class _GridURLCellState extends State { + late URLCellBloc _cellBloc; + + @override + void initState() { + final cellContext = widget.cellContextBuilder.build() as GridURLCellContext; + _cellBloc = URLCellBloc(cellContext: cellContext); + _cellBloc.add(const URLCellEvent.initial()); + _listenRequestFocus(context); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return BlocProvider.value( + value: _cellBloc, + child: BlocBuilder( + builder: (context, state) { + final richText = RichText( + textAlign: TextAlign.left, + text: TextSpan( + text: state.content, + style: TextStyle( + color: theme.main2, + fontSize: 14, + decoration: TextDecoration.underline, + ), + recognizer: _tapGesture(context), + ), + ); + + return CellEnterRegion( + child: Align(alignment: Alignment.centerLeft, child: richText), + expander: _EditCellIndicator(onTap: () {}), + ); + }, + ), + ); + } + + @override + Future dispose() async { + widget.requestFocus.removeAllListener(); + _cellBloc.close(); + super.dispose(); + } + + TapGestureRecognizer _tapGesture(BuildContext context) { + final gesture = TapGestureRecognizer(); + gesture.onTap = () async { + final url = context.read().state.url; + await _openUrlOrEdit(url); + }; + return gesture; + } + + Future _openUrlOrEdit(String url) async { + final uri = Uri.parse(url); + if (url.isNotEmpty && await canLaunchUrl(uri)) { + await launchUrl(uri); + } else { + final cellContext = widget.cellContextBuilder.build() as GridURLCellContext; + URLCellEditor.show(context, cellContext); + } + } + + void _listenRequestFocus(BuildContext context) { + widget.requestFocus.addListener(() { + _openUrlOrEdit(_cellBloc.state.url); + }); + } +} + +class _EditCellIndicator extends StatelessWidget { + final VoidCallback onTap; + const _EditCellIndicator({required this.onTap, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return FlowyIconButton( + width: 26, + onPressed: onTap, + hoverColor: theme.hover, + radius: BorderRadius.circular(4), + iconPadding: const EdgeInsets.all(5), + icon: svgWidget("editor/edit", color: theme.iconColor), + ); + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart index eb42267445..63b790b02c 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart @@ -22,6 +22,7 @@ import 'type_option/multi_select.dart'; import 'type_option/number.dart'; import 'type_option/rich_text.dart'; import 'type_option/single_select.dart'; +import 'type_option/url.dart'; typedef UpdateFieldCallback = void Function(Field, Uint8List); typedef SwitchToFieldCallback = Future> Function( @@ -168,9 +169,12 @@ TypeOptionBuilder _makeTypeOptionBuild({ typeOptionContext as RichTextTypeOptionContext, ); - default: - throw UnimplementedError; + case FieldType.URL: + return URLTypeOptionBuilder( + typeOptionContext as URLTypeOptionContext, + ); } + throw UnimplementedError; } TypeOptionContext _makeTypeOptionContext(GridFieldContext fieldContext) { @@ -205,9 +209,15 @@ TypeOptionContext _makeTypeOptionContext(GridFieldContext fieldContext) { fieldContext: fieldContext, dataBuilder: SingleSelectTypeOptionDataBuilder(), ); - default: - throw UnimplementedError(); + + case FieldType.URL: + return URLTypeOptionContext( + fieldContext: fieldContext, + dataBuilder: URLTypeOptionDataBuilder(), + ); } + + throw UnimplementedError(); } abstract class TypeOptionWidget extends StatelessWidget { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart index a4da8fa1b9..035d101544 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart @@ -17,9 +17,10 @@ extension FieldTypeListExtension on FieldType { return "grid/field/text"; case FieldType.SingleSelect: return "grid/field/single_select"; - default: - throw UnimplementedError; + case FieldType.URL: + return "grid/field/url"; } + throw UnimplementedError; } String title() { @@ -36,8 +37,9 @@ extension FieldTypeListExtension on FieldType { return LocaleKeys.grid_field_textFieldName.tr(); case FieldType.SingleSelect: return LocaleKeys.grid_field_singleSelectFieldName.tr(); - default: - throw UnimplementedError; + case FieldType.URL: + return LocaleKeys.grid_field_urlFieldName.tr(); } + throw UnimplementedError; } } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart new file mode 100644 index 0000000000..f4e73f7fdc --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart @@ -0,0 +1,20 @@ +import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; +import 'package:flutter/material.dart'; + +typedef URLTypeOptionContext = TypeOptionContext; + +class URLTypeOptionDataBuilder extends TypeOptionDataBuilder { + @override + URLTypeOption fromBuffer(List buffer) { + return URLTypeOption.fromBuffer(buffer); + } +} + +class URLTypeOptionBuilder extends TypeOptionBuilder { + URLTypeOptionBuilder(URLTypeOptionContext typeOptionContext); + + @override + Widget? get customWidget => null; +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart deleted file mode 100644 index 0f3f7c5f32..0000000000 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:app_flowy/startup/startup.dart'; -import 'package:app_flowy/workspace/application/grid/prelude.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class NumberCell extends StatefulWidget { - final GridCell cellData; - - const NumberCell({ - required this.cellData, - Key? key, - }) : super(key: key); - - @override - State createState() => _NumberCellState(); -} - -class _NumberCellState extends State { - late NumberCellBloc _cellBloc; - - @override - void initState() { - _cellBloc = getIt(param1: widget.cellData); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return BlocProvider.value( - value: _cellBloc, - child: BlocBuilder( - builder: (context, state) { - return Container(); - }, - ), - ); - } - - @override - Future dispose() async { - _cellBloc.close(); - super.dispose(); - } -} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart index 200a079d55..a643b58928 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart @@ -209,9 +209,10 @@ class _CellExpander extends StatelessWidget { return FittedBox( fit: BoxFit.contain, child: FlowyIconButton( + width: 26, onPressed: onExpand, - iconPadding: const EdgeInsets.fromLTRB(6, 6, 6, 6), - fillColor: theme.surface, + iconPadding: const EdgeInsets.all(5), + radius: BorderRadius.circular(4), icon: svgWidget("grid/expander", color: theme.main1), ), ); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart index 41cbcb1cc1..0900039b1f 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart @@ -4,6 +4,7 @@ import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart'; import 'package:app_flowy/workspace/application/grid/row/row_service.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart'; import 'package:flowy_infra/image.dart'; @@ -67,14 +68,15 @@ class _RowDetailPageState extends State { return bloc; }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 40), + padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20), child: Column( children: [ SizedBox( - height: 40, - child: Row( - children: const [Spacer(), _CloseButton()], - )), + height: 40, + child: Row( + children: const [Spacer(), _CloseButton()], + ), + ), Expanded(child: _PropertyList(cellCache: widget.cellCache)), ], ), @@ -153,24 +155,26 @@ class _RowDetailCell extends StatelessWidget { cellCache, style: _buildCellStyle(theme, gridCell.field.fieldType), ); - return SizedBox( - height: 36, - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 150, - child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)), - ), - const HSpace(10), - Expanded( - child: FlowyHover2( - child: cell, - contentPadding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), + return ConstrainedBox( + constraints: const BoxConstraints(minHeight: 40), + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 150, + child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)), ), - ), - ], + const HSpace(10), + Expanded( + child: FlowyHover2( + child: cell, + contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12), + ), + ), + ], + ), ), ); } @@ -209,7 +213,11 @@ GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) { return SelectOptionCellStyle( placeholder: LocaleKeys.grid_row_textPlaceholder.tr(), ); - default: - return null; + + case FieldType.URL: + return GridURLCellStyle( + placeholder: LocaleKeys.grid_row_textPlaceholder.tr(), + ); } + return null; } diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker.dart index f1e4420252..650cd185f1 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker.dart @@ -135,7 +135,7 @@ class _EmojiPickerState extends State { if (!loaded) { // Load emojis updateEmojiFuture.then( - (value) => WidgetsBinding.instance!.addPostFrameCallback((_) { + (value) => WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; setState(() { loaded = true; diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart index 91ce8d07ec..762a978693 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart @@ -62,8 +62,9 @@ class QuestionBubble extends StatelessWidget { } _launchURL(String url) async { - if (await canLaunch(url)) { - await launch(url); + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); } else { throw 'Could not launch $url'; } diff --git a/frontend/app_flowy/linux/flutter/generated_plugins.cmake b/frontend/app_flowy/linux/flutter/generated_plugins.cmake index 5562f19113..c7ae414da2 100644 --- a/frontend/app_flowy/linux/flutter/generated_plugins.cmake +++ b/frontend/app_flowy/linux/flutter/generated_plugins.cmake @@ -8,6 +8,9 @@ list(APPEND FLUTTER_PLUGIN_LIST window_size ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -16,3 +19,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj b/frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj index 08e2493683..2e7ab66fee 100644 --- a/frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj +++ b/frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj @@ -421,6 +421,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + EXCLUDED_ARCHS = arm64; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -552,6 +553,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + EXCLUDED_ARCHS = arm64; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -575,6 +577,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + EXCLUDED_ARCHS = arm64; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/frontend/app_flowy/macos/Runner/MainFlutterWindow.swift b/frontend/app_flowy/macos/Runner/MainFlutterWindow.swift index 2722837ec9..8e357d7ca1 100644 --- a/frontend/app_flowy/macos/Runner/MainFlutterWindow.swift +++ b/frontend/app_flowy/macos/Runner/MainFlutterWindow.swift @@ -1,12 +1,82 @@ import Cocoa import FlutterMacOS +private let kTrafficLightOffetTop = 22 + class MainFlutterWindow: NSWindow { + func registerMethodChannel(flutterViewController: FlutterViewController) { + let cocoaWindowChannel = FlutterMethodChannel(name: "flutter/cocoaWindow", binaryMessenger: flutterViewController.engine.binaryMessenger) + cocoaWindowChannel.setMethodCallHandler({ + (call: FlutterMethodCall, result: FlutterResult) -> Void in + if call.method == "setWindowPosition" { + guard let position = call.arguments as? NSArray else { + result(nil) + return + } + let nX = position[0] as! NSNumber + let nY = position[1] as! NSNumber + let x = nX.doubleValue + let y = nY.doubleValue + + self.setFrameOrigin(NSPoint(x: x, y: y)) + result(nil) + return + } else if call.method == "getWindowPosition" { + let frame = self.frame + result([frame.origin.x, frame.origin.y]) + return + } else if call.method == "zoom" { + self.zoom(self) + result(nil) + return + } + + result(FlutterMethodNotImplemented) + }) + } + + func layoutTrafficLightButton(titlebarView: NSView, button: NSButton, offsetTop: CGFloat, offsetLeft: CGFloat) { + button.translatesAutoresizingMaskIntoConstraints = false; + titlebarView.addConstraint(NSLayoutConstraint.init( + item: button, + attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: titlebarView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: offsetTop)) + titlebarView.addConstraint(NSLayoutConstraint.init( + item: button, + attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: titlebarView, attribute: NSLayoutConstraint.Attribute.left, multiplier: 1, constant: offsetLeft)) + } + + func layoutTrafficLights() { + let closeButton = self.standardWindowButton(ButtonType.closeButton)! + let minButton = self.standardWindowButton(ButtonType.miniaturizeButton)! + let zoomButton = self.standardWindowButton(ButtonType.zoomButton)! + let titlebarView = closeButton.superview! + + self.layoutTrafficLightButton(titlebarView: titlebarView, button: closeButton, offsetTop: CGFloat(kTrafficLightOffetTop), offsetLeft: 20) + self.layoutTrafficLightButton(titlebarView: titlebarView, button: minButton, offsetTop: CGFloat(kTrafficLightOffetTop), offsetLeft: 38) + self.layoutTrafficLightButton(titlebarView: titlebarView, button: zoomButton, offsetTop: CGFloat(kTrafficLightOffetTop), offsetLeft: 56) + + let customToolbar = NSTitlebarAccessoryViewController() + let newView = NSView() + newView.frame = NSRect(origin: CGPoint(), size: CGSize(width: 0, height: 40)) // only the height is cared + customToolbar.view = newView + self.addTitlebarAccessoryViewController(customToolbar) + } + override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController + + self.registerMethodChannel(flutterViewController: flutterViewController) + self.setFrame(windowFrame, display: true) + self.titlebarAppearsTransparent = true + self.titleVisibility = .hidden + self.styleMask.insert(StyleMask.fullSizeContentView) + self.isMovableByWindowBackground = true + self.isMovable = false + + self.layoutTrafficLights() RegisterGeneratedPlugins(registry: flutterViewController) diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart index 4f06a40b9b..bb7144974b 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart @@ -102,14 +102,14 @@ class FlowyHoverContainer extends StatelessWidget { } // -abstract class HoverWidget extends StatefulWidget { - const HoverWidget({Key? key}) : super(key: key); +abstract class FlowyHoverWidget extends Widget { + const FlowyHoverWidget({Key? key}) : super(key: key); - ValueNotifier get onFocus; + ValueNotifier? get onFocus; } class FlowyHover2 extends StatefulWidget { - final Widget child; + final FlowyHoverWidget child; final EdgeInsets contentPadding; const FlowyHover2({ required this.child, @@ -123,24 +123,30 @@ class FlowyHover2 extends StatefulWidget { class _FlowyHover2State extends State { late FlowyHoverState _hoverState; + VoidCallback? _listenerFn; @override void initState() { _hoverState = FlowyHoverState(); - if (widget.child is HoverWidget) { - final hoverWidget = widget.child as HoverWidget; - hoverWidget.onFocus.addListener(() { - _hoverState.onFocus = hoverWidget.onFocus.value; - }); + listener() { + _hoverState.onFocus = widget.child.onFocus?.value ?? false; } + _listenerFn = listener; + widget.child.onFocus?.addListener(listener); + super.initState(); } @override void dispose() { _hoverState.dispose(); + + if (_listenerFn != null) { + widget.child.onFocus?.removeListener(_listenerFn!); + _listenerFn = null; + } super.dispose(); } @@ -179,10 +185,7 @@ class _HoverBackground extends StatelessWidget { builder: (context, state, child) { if (state.onHover || state.onFocus) { return FlowyHoverContainer( - style: HoverStyle( - borderRadius: Corners.s6Border, - hoverColor: theme.shader6, - ), + style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: theme.shader6), ); } else { return const SizedBox(); diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart index 712fba3c6c..37eb782a2c 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart @@ -132,9 +132,12 @@ class _RoundedInputFieldState extends State { children.add( Align( alignment: Alignment.centerLeft, - child: Text( - widget.errorText, - style: widget.style, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Text( + widget.errorText, + style: widget.style, + ), ), ), ); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart index bf4964054e..0149f63b3a 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart @@ -392,20 +392,3 @@ class GridEventUpdateDateCell { } } -class GridEventGetDateCellData { - CellIdentifierPayload request; - GridEventGetDateCellData(this.request); - - Future> send() { - final request = FFIRequest.create() - ..event = GridEvent.GetDateCellData.toString() - ..payload = requestToBytes(this.request); - - return Dispatch.asyncRequest(request) - .then((bytesResult) => bytesResult.fold( - (okBytes) => left(DateCellData.fromBuffer(okBytes)), - (errBytes) => right(FlowyError.fromBuffer(errBytes)), - )); - } -} - diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart index a1ecd8c059..87f9036c88 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart @@ -1366,24 +1366,19 @@ class GridBlock extends $pb.GeneratedMessage { class Cell extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Cell', createEmptyInstance: create) ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId') - ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'content') - ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data') + ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY) ..hasRequiredFields = false ; Cell._() : super(); factory Cell({ $core.String? fieldId, - $core.String? content, - $core.String? data, + $core.List<$core.int>? data, }) { final _result = create(); if (fieldId != null) { _result.fieldId = fieldId; } - if (content != null) { - _result.content = content; - } if (data != null) { _result.data = data; } @@ -1420,22 +1415,13 @@ class Cell extends $pb.GeneratedMessage { void clearFieldId() => clearField(1); @$pb.TagNumber(2) - $core.String get content => $_getSZ(1); + $core.List<$core.int> get data => $_getN(1); @$pb.TagNumber(2) - set content($core.String v) { $_setString(1, v); } + set data($core.List<$core.int> v) { $_setBytes(1, v); } @$pb.TagNumber(2) - $core.bool hasContent() => $_has(1); + $core.bool hasData() => $_has(1); @$pb.TagNumber(2) - void clearContent() => clearField(2); - - @$pb.TagNumber(3) - $core.String get data => $_getSZ(2); - @$pb.TagNumber(3) - set data($core.String v) { $_setString(2, v); } - @$pb.TagNumber(3) - $core.bool hasData() => $_has(2); - @$pb.TagNumber(3) - void clearData() => clearField(3); + void clearData() => clearField(2); } class RepeatedCell extends $pb.GeneratedMessage { diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbenum.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbenum.dart index e6cb17314b..78331a46e5 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbenum.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbenum.dart @@ -31,6 +31,7 @@ class FieldType extends $pb.ProtobufEnum { static const FieldType SingleSelect = FieldType._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SingleSelect'); static const FieldType MultiSelect = FieldType._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MultiSelect'); static const FieldType Checkbox = FieldType._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Checkbox'); + static const FieldType URL = FieldType._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'URL'); static const $core.List values = [ RichText, @@ -39,6 +40,7 @@ class FieldType extends $pb.ProtobufEnum { SingleSelect, MultiSelect, Checkbox, + URL, ]; static final $core.Map<$core.int, FieldType> _byValue = $pb.ProtobufEnum.initByValue(values); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart index 3a925cb282..7c28fa1ceb 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart @@ -29,11 +29,12 @@ const FieldType$json = const { const {'1': 'SingleSelect', '2': 3}, const {'1': 'MultiSelect', '2': 4}, const {'1': 'Checkbox', '2': 5}, + const {'1': 'URL', '2': 6}, ], }; /// Descriptor for `FieldType`. Decode as a `google.protobuf.EnumDescriptorProto`. -final $typed_data.Uint8List fieldTypeDescriptor = $convert.base64Decode('CglGaWVsZFR5cGUSDAoIUmljaFRleHQQABIKCgZOdW1iZXIQARIMCghEYXRlVGltZRACEhAKDFNpbmdsZVNlbGVjdBADEg8KC011bHRpU2VsZWN0EAQSDAoIQ2hlY2tib3gQBQ=='); +final $typed_data.Uint8List fieldTypeDescriptor = $convert.base64Decode('CglGaWVsZFR5cGUSDAoIUmljaFRleHQQABIKCgZOdW1iZXIQARIMCghEYXRlVGltZRACEhAKDFNpbmdsZVNlbGVjdBADEg8KC011bHRpU2VsZWN0EAQSDAoIQ2hlY2tib3gQBRIHCgNVUkwQBg=='); @$core.Deprecated('Use gridDescriptor instead') const Grid$json = const { '1': 'Grid', @@ -289,13 +290,12 @@ const Cell$json = const { '1': 'Cell', '2': const [ const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'}, - const {'1': 'content', '3': 2, '4': 1, '5': 9, '10': 'content'}, - const {'1': 'data', '3': 3, '4': 1, '5': 9, '10': 'data'}, + const {'1': 'data', '3': 2, '4': 1, '5': 12, '10': 'data'}, ], }; /// Descriptor for `Cell`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List cellDescriptor = $convert.base64Decode('CgRDZWxsEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEhgKB2NvbnRlbnQYAiABKAlSB2NvbnRlbnQSEgoEZGF0YRgDIAEoCVIEZGF0YQ=='); +final $typed_data.Uint8List cellDescriptor = $convert.base64Decode('CgRDZWxsEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEhIKBGRhdGEYAiABKAxSBGRhdGE='); @$core.Deprecated('Use repeatedCellDescriptor instead') const RepeatedCell$json = const { '1': 'RepeatedCell', diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart index 341dcf8d6a..863162cc3e 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart @@ -33,7 +33,6 @@ class GridEvent extends $pb.ProtobufEnum { static const GridEvent UpdateCell = GridEvent._(71, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateCell'); static const GridEvent UpdateSelectOptionCell = GridEvent._(72, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateSelectOptionCell'); static const GridEvent UpdateDateCell = GridEvent._(80, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateDateCell'); - static const GridEvent GetDateCellData = GridEvent._(90, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetDateCellData'); static const $core.List values = [ GetGridData, @@ -59,7 +58,6 @@ class GridEvent extends $pb.ProtobufEnum { UpdateCell, UpdateSelectOptionCell, UpdateDateCell, - GetDateCellData, ]; static final $core.Map<$core.int, GridEvent> _byValue = $pb.ProtobufEnum.initByValue(values); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart index 58a712a4da..08de369a01 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart @@ -35,9 +35,8 @@ const GridEvent$json = const { const {'1': 'UpdateCell', '2': 71}, const {'1': 'UpdateSelectOptionCell', '2': 72}, const {'1': 'UpdateDateCell', '2': 80}, - const {'1': 'GetDateCellData', '2': 90}, ], }; /// Descriptor for `GridEvent`. Decode as a `google.protobuf.EnumDescriptorProto`. -final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIZChVVcGRhdGVGaWVsZFR5cGVPcHRpb24QDBIPCgtJbnNlcnRGaWVsZBANEg8KC0RlbGV0ZUZpZWxkEA4SEQoNU3dpdGNoVG9GaWVsZBAUEhIKDkR1cGxpY2F0ZUZpZWxkEBUSDAoITW92ZUl0ZW0QFhIWChJHZXRGaWVsZFR5cGVPcHRpb24QFxIZChVDcmVhdGVGaWVsZFR5cGVPcHRpb24QGBITCg9OZXdTZWxlY3RPcHRpb24QHhIbChdHZXRTZWxlY3RPcHRpb25DZWxsRGF0YRAfEhYKElVwZGF0ZVNlbGVjdE9wdGlvbhAgEg0KCUNyZWF0ZVJvdxAyEgoKBkdldFJvdxAzEg0KCURlbGV0ZVJvdxA0EhAKDER1cGxpY2F0ZVJvdxA1EgsKB0dldENlbGwQRhIOCgpVcGRhdGVDZWxsEEcSGgoWVXBkYXRlU2VsZWN0T3B0aW9uQ2VsbBBIEhIKDlVwZGF0ZURhdGVDZWxsEFASEwoPR2V0RGF0ZUNlbGxEYXRhEFo='); +final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIZChVVcGRhdGVGaWVsZFR5cGVPcHRpb24QDBIPCgtJbnNlcnRGaWVsZBANEg8KC0RlbGV0ZUZpZWxkEA4SEQoNU3dpdGNoVG9GaWVsZBAUEhIKDkR1cGxpY2F0ZUZpZWxkEBUSDAoITW92ZUl0ZW0QFhIWChJHZXRGaWVsZFR5cGVPcHRpb24QFxIZChVDcmVhdGVGaWVsZFR5cGVPcHRpb24QGBITCg9OZXdTZWxlY3RPcHRpb24QHhIbChdHZXRTZWxlY3RPcHRpb25DZWxsRGF0YRAfEhYKElVwZGF0ZVNlbGVjdE9wdGlvbhAgEg0KCUNyZWF0ZVJvdxAyEgoKBkdldFJvdxAzEg0KCURlbGV0ZVJvdxA0EhAKDER1cGxpY2F0ZVJvdxA1EgsKB0dldENlbGwQRhIOCgpVcGRhdGVDZWxsEEcSGgoWVXBkYXRlU2VsZWN0T3B0aW9uQ2VsbBBIEhIKDlVwZGF0ZURhdGVDZWxsEFA='); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/protobuf.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/protobuf.dart index af6583c106..c056e2799a 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/protobuf.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/protobuf.dart @@ -5,6 +5,7 @@ export './dart_notification.pb.dart'; export './selection_type_option.pb.dart'; export './row_entities.pb.dart'; export './cell_entities.pb.dart'; +export './url_type_option.pb.dart'; export './checkbox_type_option.pb.dart'; export './event_map.pb.dart'; export './text_type_option.pb.dart'; diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pb.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pb.dart index c30f2eb6e1..a38a68be36 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pb.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pb.dart @@ -11,17 +11,17 @@ import 'package:protobuf/protobuf.dart' as $pb; class RichTextTypeOption extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RichTextTypeOption', createEmptyInstance: create) - ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'format') + ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data') ..hasRequiredFields = false ; RichTextTypeOption._() : super(); factory RichTextTypeOption({ - $core.String? format, + $core.String? data, }) { final _result = create(); - if (format != null) { - _result.format = format; + if (data != null) { + _result.data = data; } return _result; } @@ -47,12 +47,12 @@ class RichTextTypeOption extends $pb.GeneratedMessage { static RichTextTypeOption? _defaultInstance; @$pb.TagNumber(1) - $core.String get format => $_getSZ(0); + $core.String get data => $_getSZ(0); @$pb.TagNumber(1) - set format($core.String v) { $_setString(0, v); } + set data($core.String v) { $_setString(0, v); } @$pb.TagNumber(1) - $core.bool hasFormat() => $_has(0); + $core.bool hasData() => $_has(0); @$pb.TagNumber(1) - void clearFormat() => clearField(1); + void clearData() => clearField(1); } diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pbjson.dart index e4ba6956ee..5999ce87e0 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pbjson.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pbjson.dart @@ -12,9 +12,9 @@ import 'dart:typed_data' as $typed_data; const RichTextTypeOption$json = const { '1': 'RichTextTypeOption', '2': const [ - const {'1': 'format', '3': 1, '4': 1, '5': 9, '10': 'format'}, + const {'1': 'data', '3': 1, '4': 1, '5': 9, '10': 'data'}, ], }; /// Descriptor for `RichTextTypeOption`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List richTextTypeOptionDescriptor = $convert.base64Decode('ChJSaWNoVGV4dFR5cGVPcHRpb24SFgoGZm9ybWF0GAEgASgJUgZmb3JtYXQ='); +final $typed_data.Uint8List richTextTypeOptionDescriptor = $convert.base64Decode('ChJSaWNoVGV4dFR5cGVPcHRpb24SEgoEZGF0YRgBIAEoCVIEZGF0YQ=='); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pb.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pb.dart new file mode 100644 index 0000000000..c43474a92a --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pb.dart @@ -0,0 +1,119 @@ +/// +// Generated code. Do not modify. +// source: url_type_option.proto +// +// @dart = 2.12 +// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +class URLTypeOption extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'URLTypeOption', createEmptyInstance: create) + ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data') + ..hasRequiredFields = false + ; + + URLTypeOption._() : super(); + factory URLTypeOption({ + $core.String? data, + }) { + final _result = create(); + if (data != null) { + _result.data = data; + } + return _result; + } + factory URLTypeOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory URLTypeOption.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + URLTypeOption clone() => URLTypeOption()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + URLTypeOption copyWith(void Function(URLTypeOption) updates) => super.copyWith((message) => updates(message as URLTypeOption)) as URLTypeOption; // ignore: deprecated_member_use + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static URLTypeOption create() => URLTypeOption._(); + URLTypeOption createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static URLTypeOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static URLTypeOption? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get data => $_getSZ(0); + @$pb.TagNumber(1) + set data($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasData() => $_has(0); + @$pb.TagNumber(1) + void clearData() => clearField(1); +} + +class URLCellData extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'URLCellData', createEmptyInstance: create) + ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'url') + ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'content') + ..hasRequiredFields = false + ; + + URLCellData._() : super(); + factory URLCellData({ + $core.String? url, + $core.String? content, + }) { + final _result = create(); + if (url != null) { + _result.url = url; + } + if (content != null) { + _result.content = content; + } + return _result; + } + factory URLCellData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory URLCellData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + URLCellData clone() => URLCellData()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + URLCellData copyWith(void Function(URLCellData) updates) => super.copyWith((message) => updates(message as URLCellData)) as URLCellData; // ignore: deprecated_member_use + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static URLCellData create() => URLCellData._(); + URLCellData createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static URLCellData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static URLCellData? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get url => $_getSZ(0); + @$pb.TagNumber(1) + set url($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasUrl() => $_has(0); + @$pb.TagNumber(1) + void clearUrl() => clearField(1); + + @$pb.TagNumber(2) + $core.String get content => $_getSZ(1); + @$pb.TagNumber(2) + set content($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasContent() => $_has(1); + @$pb.TagNumber(2) + void clearContent() => clearField(2); +} + diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbenum.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbenum.dart new file mode 100644 index 0000000000..de8793d432 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbenum.dart @@ -0,0 +1,7 @@ +/// +// Generated code. Do not modify. +// source: url_type_option.proto +// +// @dart = 2.12 +// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields + diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbjson.dart new file mode 100644 index 0000000000..30ac81dfb2 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbjson.dart @@ -0,0 +1,31 @@ +/// +// Generated code. Do not modify. +// source: url_type_option.proto +// +// @dart = 2.12 +// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package + +import 'dart:core' as $core; +import 'dart:convert' as $convert; +import 'dart:typed_data' as $typed_data; +@$core.Deprecated('Use uRLTypeOptionDescriptor instead') +const URLTypeOption$json = const { + '1': 'URLTypeOption', + '2': const [ + const {'1': 'data', '3': 1, '4': 1, '5': 9, '10': 'data'}, + ], +}; + +/// Descriptor for `URLTypeOption`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List uRLTypeOptionDescriptor = $convert.base64Decode('Cg1VUkxUeXBlT3B0aW9uEhIKBGRhdGEYASABKAlSBGRhdGE='); +@$core.Deprecated('Use uRLCellDataDescriptor instead') +const URLCellData$json = const { + '1': 'URLCellData', + '2': const [ + const {'1': 'url', '3': 1, '4': 1, '5': 9, '10': 'url'}, + const {'1': 'content', '3': 2, '4': 1, '5': 9, '10': 'content'}, + ], +}; + +/// Descriptor for `URLCellData`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List uRLCellDataDescriptor = $convert.base64Decode('CgtVUkxDZWxsRGF0YRIQCgN1cmwYASABKAlSA3VybBIYCgdjb250ZW50GAIgASgJUgdjb250ZW50'); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbserver.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbserver.dart new file mode 100644 index 0000000000..6889e31393 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbserver.dart @@ -0,0 +1,9 @@ +/// +// Generated code. Do not modify. +// source: url_type_option.proto +// +// @dart = 2.12 +// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package + +export 'url_type_option.pb.dart'; + diff --git a/frontend/app_flowy/pubspec.lock b/frontend/app_flowy/pubspec.lock index 8f7e6d6db2..958debd9dd 100644 --- a/frontend/app_flowy/pubspec.lock +++ b/frontend/app_flowy/pubspec.lock @@ -7,28 +7,28 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "31.0.0" + version: "38.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "2.8.0" + version: "3.4.1" animations: dependency: transitive description: name: animations url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.3" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "2.3.0" + version: "2.3.1" async: dependency: transitive description: @@ -42,14 +42,14 @@ packages: name: bloc url: "https://pub.dartlang.org" source: hosted - version: "8.0.2" + version: "8.0.3" bloc_test: dependency: "direct dev" description: name: bloc_test url: "https://pub.dartlang.org" source: hosted - version: "9.0.2" + version: "9.0.3" boolean_selector: dependency: transitive description: @@ -63,7 +63,7 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.3.0" build_config: dependency: transitive description: @@ -77,21 +77,21 @@ packages: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.1.0" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.8" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "2.1.7" + version: "2.1.11" build_runner_core: dependency: transitive description: @@ -112,7 +112,7 @@ packages: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "8.1.4" + version: "8.3.2" characters: dependency: transitive description: @@ -134,13 +134,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" - cli_util: - dependency: transitive - description: - name: cli_util - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.5" clipboard: dependency: "direct main" description: @@ -168,7 +161,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" connectivity_plus: dependency: "direct main" description: @@ -182,14 +175,14 @@ packages: name: connectivity_plus_linux url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" connectivity_plus_macos: dependency: transitive description: name: connectivity_plus_macos url: "https://pub.dartlang.org" source: hosted - version: "1.2.1" + version: "1.2.2" connectivity_plus_platform_interface: dependency: transitive description: @@ -224,21 +217,21 @@ packages: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.3.2" cross_file: dependency: transitive description: name: cross_file url: "https://pub.dartlang.org" source: hosted - version: "0.3.2" + version: "0.3.3+1" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" csslib: dependency: transitive description: @@ -259,7 +252,7 @@ packages: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.2.3" dartz: dependency: transitive description: @@ -273,14 +266,14 @@ packages: name: dbus url: "https://pub.dartlang.org" source: hosted - version: "0.6.8" + version: "0.7.3" device_info_plus: dependency: "direct main" description: name: device_info_plus url: "https://pub.dartlang.org" source: hosted - version: "3.2.1" + version: "3.2.3" device_info_plus_linux: dependency: transitive description: @@ -294,7 +287,7 @@ packages: name: device_info_plus_macos url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.2.3" device_info_plus_platform_interface: dependency: transitive description: @@ -329,7 +322,7 @@ packages: name: easy_localization url: "https://pub.dartlang.org" source: hosted - version: "3.0.1-dev" + version: "3.0.1" easy_logger: dependency: transitive description: @@ -357,14 +350,14 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" ffi: dependency: transitive description: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.2.1" file: dependency: transitive description: @@ -378,7 +371,7 @@ packages: name: fixnum url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" flowy_infra: dependency: "direct main" description: @@ -439,14 +432,14 @@ packages: name: flutter_inappwebview url: "https://pub.dartlang.org" source: hosted - version: "5.3.2" + version: "5.4.3+7" flutter_keyboard_visibility: dependency: transitive description: name: flutter_keyboard_visibility url: "https://pub.dartlang.org" source: hosted - version: "5.1.1" + version: "5.2.0" flutter_keyboard_visibility_platform_interface: dependency: transitive description: @@ -479,13 +472,13 @@ packages: name: flutter_plugin_android_lifecycle url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.6" flutter_quill: dependency: "direct main" description: path: "." - ref: dbc309f5e382963fa0122409a0aaf8e1417d51c1 - resolved-ref: dbc309f5e382963fa0122409a0aaf8e1417d51c1 + ref: "306fd78b7a134abdde0fed6be67f59e8a6068509" + resolved-ref: "306fd78b7a134abdde0fed6be67f59e8a6068509" url: "https://github.com/appflowy/flutter-quill.git" source: git version: "2.0.13" @@ -519,21 +512,21 @@ packages: name: freezed url: "https://pub.dartlang.org" source: hosted - version: "0.14.5" + version: "2.0.3+1" freezed_annotation: dependency: "direct main" description: name: freezed_annotation url: "https://pub.dartlang.org" source: hosted - version: "0.14.3" + version: "2.0.3" frontend_server_client: dependency: transitive description: name: frontend_server_client url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "2.1.3" get_it: dependency: "direct main" description: @@ -582,42 +575,56 @@ packages: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.2.0" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.0.1" i18n_extension: dependency: transitive description: name: i18n_extension url: "https://pub.dartlang.org" source: hosted - version: "4.2.0" + version: "4.2.1" image_picker: dependency: transitive description: name: image_picker url: "https://pub.dartlang.org" source: hosted - version: "0.8.4+4" + version: "0.8.5+3" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.4+13" image_picker_for_web: dependency: transitive description: name: image_picker_for_web url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "2.1.8" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.5+5" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.4.3" + version: "2.5.0" intl: dependency: "direct main" description: @@ -645,14 +652,14 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3" + version: "0.6.4" json_annotation: dependency: transitive description: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "4.4.0" + version: "4.5.0" linked_scroll_controller: dependency: "direct main" description: @@ -666,7 +673,7 @@ packages: name: lint url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" lints: dependency: transitive description: @@ -680,7 +687,7 @@ packages: name: loading_indicator url: "https://pub.dartlang.org" source: hosted - version: "3.0.2" + version: "3.1.0" logger: dependency: transitive description: @@ -708,7 +715,7 @@ packages: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "0.1.4" meta: dependency: transitive description: @@ -722,14 +729,14 @@ packages: name: mime url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" mocktail: dependency: transitive description: name: mocktail url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "0.3.0" nested: dependency: transitive description: @@ -743,7 +750,7 @@ packages: name: nm url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.5.0" node_preamble: dependency: transitive description: @@ -764,14 +771,14 @@ packages: name: package_info_plus url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.4.2" package_info_plus_linux: dependency: transitive description: name: package_info_plus_linux url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.5" package_info_plus_macos: dependency: transitive description: @@ -792,21 +799,21 @@ packages: name: package_info_plus_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.5" package_info_plus_windows: dependency: transitive description: name: package_info_plus_windows url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.5" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" path_drawing: dependency: transitive description: @@ -827,49 +834,49 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.0.10" path_provider_android: dependency: transitive description: name: path_provider_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.11" + version: "2.0.14" path_provider_ios: dependency: transitive description: name: path_provider_ios url: "https://pub.dartlang.org" source: hosted - version: "2.0.7" + version: "2.0.9" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "2.1.6" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.6" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.4" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.6" pedantic: dependency: transitive description: @@ -883,7 +890,7 @@ packages: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "4.4.0" + version: "5.0.0" photo_view: dependency: transitive description: @@ -932,14 +939,14 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "6.0.2" + version: "6.0.3" pub_semver: dependency: transitive description: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" pubspec_parse: dependency: transitive description: @@ -953,49 +960,49 @@ packages: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "3.0.1+1" + version: "3.1.0" reorderables: dependency: "direct main" description: name: reorderables url: "https://pub.dartlang.org" source: hosted - version: "0.4.3" + version: "0.5.0" shared_preferences: dependency: transitive description: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "2.0.12" + version: "2.0.15" shared_preferences_android: dependency: transitive description: name: shared_preferences_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.10" + version: "2.0.12" shared_preferences_ios: dependency: transitive description: name: shared_preferences_ios url: "https://pub.dartlang.org" source: hosted - version: "2.0.9" + version: "2.1.1" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "2.1.1" shared_preferences_macos: dependency: transitive description: name: shared_preferences_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.4" shared_preferences_platform_interface: dependency: transitive description: @@ -1009,21 +1016,21 @@ packages: name: shared_preferences_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.4" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "2.1.1" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" shelf_packages_handler: dependency: transitive description: @@ -1070,7 +1077,7 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "1.2.1" + version: "1.2.2" source_map_stack_trace: dependency: transitive description: @@ -1091,7 +1098,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" sprintf: dependency: transitive description: @@ -1161,28 +1168,28 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.19.5" + version: "1.21.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.8" + version: "0.4.9" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.13" textfield_tags: dependency: "direct main" description: name: textfield_tags url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.0+1" textstyle_extensions: dependency: transitive description: @@ -1217,7 +1224,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" universal_platform: dependency: transitive description: @@ -1231,35 +1238,35 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "6.0.18" + version: "6.1.2" url_launcher_android: dependency: transitive description: name: url_launcher_android url: "https://pub.dartlang.org" source: hosted - version: "6.0.14" + version: "6.0.17" url_launcher_ios: dependency: transitive description: name: url_launcher_ios url: "https://pub.dartlang.org" source: hosted - version: "6.0.14" + version: "6.0.17" url_launcher_linux: dependency: transitive description: name: url_launcher_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "3.0.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "3.0.1" url_launcher_platform_interface: dependency: transitive description: @@ -1273,56 +1280,70 @@ packages: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.11" url_launcher_windows: dependency: transitive description: name: url_launcher_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "3.0.1" uuid: dependency: transitive description: name: uuid url: "https://pub.dartlang.org" source: hosted - version: "3.0.5" + version: "3.0.6" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" video_player: dependency: transitive description: name: video_player url: "https://pub.dartlang.org" source: hosted - version: "2.2.11" + version: "2.4.2" + video_player_android: + dependency: transitive + description: + name: video_player_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.4" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.4" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "5.0.1" + version: "5.1.2" video_player_web: dependency: transitive description: name: video_player_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.10" vm_service: dependency: transitive description: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "7.5.0" + version: "8.3.0" watcher: dependency: transitive description: @@ -1336,21 +1357,21 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.2.0" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.1.0" win32: dependency: transitive description: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.3.6" + version: "2.6.1" window_size: dependency: "direct main" description: @@ -1366,28 +1387,28 @@ packages: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "0.2.0+1" xml: dependency: transitive description: name: xml url: "https://pub.dartlang.org" source: hosted - version: "5.3.1" + version: "5.4.1" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "3.1.1" youtube_player_flutter: dependency: transitive description: name: youtube_player_flutter url: "https://pub.dartlang.org" source: hosted - version: "8.0.0" + version: "8.1.0" sdks: - dart: ">=2.15.0-116.0.dev <3.0.0" - flutter: ">=2.5.0" + dart: ">=2.17.0 <3.0.0" + flutter: ">=3.0.0" diff --git a/frontend/app_flowy/pubspec.yaml b/frontend/app_flowy/pubspec.yaml index fd2b75b9e0..eb7b17dc21 100644 --- a/frontend/app_flowy/pubspec.yaml +++ b/frontend/app_flowy/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.15.0-116.0.dev <3.0.0" + sdk: ">=2.16.2 <3.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -40,7 +40,7 @@ dependencies: flutter_quill: git: url: https://github.com/appflowy/flutter-quill.git - ref: dbc309f5e382963fa0122409a0aaf8e1417d51c1 + ref: 306fd78b7a134abdde0fed6be67f59e8a6068509 # third party packages intl: ^0.17.0 @@ -74,7 +74,7 @@ dependencies: device_info_plus: ^3.2.1 fluttertoast: ^8.0.8 table_calendar: ^3.0.5 - reorderables: + reorderables: ^0.5.0 linked_scroll_controller: ^0.2.0 dev_dependencies: diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 554def4acd..393a02ac73 100755 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -928,6 +928,7 @@ dependencies = [ "dart-notify", "dashmap", "diesel", + "fancy-regex", "flowy-database", "flowy-derive", "flowy-error", @@ -953,6 +954,7 @@ dependencies = [ "strum_macros", "tokio", "tracing", + "url", ] [[package]] diff --git a/frontend/rust-lib/flowy-folder/src/manager.rs b/frontend/rust-lib/flowy-folder/src/manager.rs index 05847f8cc1..fe9a160b39 100644 --- a/frontend/rust-lib/flowy-folder/src/manager.rs +++ b/frontend/rust-lib/flowy-folder/src/manager.rs @@ -4,7 +4,7 @@ use crate::{ errors::FlowyResult, event_map::{FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser}, services::{ - folder_editor::ClientFolderEditor, persistence::FolderPersistence, set_current_workspace, AppController, + folder_editor::FolderEditor, persistence::FolderPersistence, set_current_workspace, AppController, TrashController, ViewController, WorkspaceController, }, }; @@ -61,7 +61,7 @@ pub struct FolderManager { pub(crate) view_controller: Arc, pub(crate) trash_controller: Arc, web_socket: Arc, - folder_editor: Arc>>>, + folder_editor: Arc>>>, data_processors: ViewDataProcessorMap, } @@ -166,8 +166,7 @@ impl FolderManager { let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), disk_cache)); let rev_manager = RevisionManager::new(user_id, folder_id.as_ref(), rev_persistence); - let folder_editor = - ClientFolderEditor::new(user_id, &folder_id, token, rev_manager, self.web_socket.clone()).await?; + let folder_editor = FolderEditor::new(user_id, &folder_id, token, rev_manager, self.web_socket.clone()).await?; *self.folder_editor.write().await = Some(Arc::new(folder_editor)); let _ = self.app_controller.initialize()?; @@ -228,7 +227,7 @@ impl DefaultFolderBuilder { #[cfg(feature = "flowy_unit_test")] impl FolderManager { - pub async fn folder_editor(&self) -> Arc { + pub async fn folder_editor(&self) -> Arc { self.folder_editor.read().await.clone().unwrap() } } diff --git a/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs index 45034646b0..b95b91dc2e 100644 --- a/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs +++ b/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs @@ -46,7 +46,7 @@ pub(crate) async fn update_app_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, app_controller, view_controller))] +#[tracing::instrument(level = "trace", skip(data, app_controller, view_controller))] pub(crate) async fn read_app_handler( data: Data, app_controller: AppData>, diff --git a/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs b/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs index 6ea1a7e2ec..ef94c1d316 100644 --- a/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs +++ b/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs @@ -17,7 +17,7 @@ use lib_ot::core::PlainTextAttributes; use parking_lot::RwLock; use std::sync::Arc; -pub struct ClientFolderEditor { +pub struct FolderEditor { user_id: String, #[allow(dead_code)] pub(crate) folder_id: FolderId, @@ -27,7 +27,7 @@ pub struct ClientFolderEditor { ws_manager: Arc, } -impl ClientFolderEditor { +impl FolderEditor { #[allow(unused_variables)] pub async fn new( user_id: &str, @@ -129,7 +129,7 @@ impl RevisionCloudService for FolderRevisionCloudService { } #[cfg(feature = "flowy_unit_test")] -impl ClientFolderEditor { +impl FolderEditor { pub fn rev_manager(&self) -> Arc { self.rev_manager.clone() } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs index b272168e33..d5bbb3a277 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs @@ -5,7 +5,7 @@ mod version_2; use crate::{ event_map::WorkspaceDatabase, manager::FolderId, - services::{folder_editor::ClientFolderEditor, persistence::migration::FolderMigration}, + services::{folder_editor::FolderEditor, persistence::migration::FolderMigration}, }; use flowy_database::ConnectionPool; use flowy_error::{FlowyError, FlowyResult}; @@ -50,14 +50,11 @@ pub trait FolderPersistenceTransaction { pub struct FolderPersistence { database: Arc, - folder_editor: Arc>>>, + folder_editor: Arc>>>, } impl FolderPersistence { - pub fn new( - database: Arc, - folder_editor: Arc>>>, - ) -> Self { + pub fn new(database: Arc, folder_editor: Arc>>>) -> Self { Self { database, folder_editor, diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs index 8dafda3c93..40f72e10b0 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs @@ -1,5 +1,5 @@ use crate::services::{ - folder_editor::ClientFolderEditor, + folder_editor::FolderEditor, persistence::{AppChangeset, FolderPersistenceTransaction, ViewChangeset, WorkspaceChangeset}, }; use flowy_error::{FlowyError, FlowyResult}; @@ -11,7 +11,7 @@ use flowy_folder_data_model::entities::{ }; use std::sync::Arc; -impl FolderPersistenceTransaction for ClientFolderEditor { +impl FolderPersistenceTransaction for FolderEditor { fn create_workspace(&self, _user_id: &str, workspace: Workspace) -> FlowyResult<()> { if let Some(change) = self.folder.write().create_workspace(workspace)? { let _ = self.apply_change(change)?; diff --git a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs b/frontend/rust-lib/flowy-folder/src/services/view/controller.rs index 603b76ec2c..ae88073afc 100644 --- a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/view/controller.rs @@ -129,7 +129,7 @@ impl ViewController { .await } - #[tracing::instrument(level = "debug", skip(self), err)] + #[tracing::instrument(level = "trace", skip(self), err)] pub(crate) fn set_latest_view(&self, view_id: &str) -> Result<(), FlowyError> { KV::set_str(LATEST_VIEW_ID, view_id.to_owned()); Ok(()) @@ -193,7 +193,7 @@ impl ViewController { } // belong_to_id will be the app_id or view_id. - #[tracing::instrument(level = "debug", skip(self), err)] + #[tracing::instrument(level = "trace", skip(self), err)] pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result { self.persistence .begin_transaction(|transaction| { diff --git a/frontend/rust-lib/flowy-folder/tests/workspace/script.rs b/frontend/rust-lib/flowy-folder/tests/workspace/script.rs index a6fd1fbe29..39f023ca7d 100644 --- a/frontend/rust-lib/flowy-folder/tests/workspace/script.rs +++ b/frontend/rust-lib/flowy-folder/tests/workspace/script.rs @@ -1,5 +1,5 @@ use flowy_folder::event_map::FolderEvent::*; -use flowy_folder::{errors::ErrorCode, services::folder_editor::ClientFolderEditor}; +use flowy_folder::{errors::ErrorCode, services::folder_editor::FolderEditor}; use flowy_folder_data_model::entities::view::{RepeatedViewId, ViewId}; use flowy_folder_data_model::entities::workspace::WorkspaceId; use flowy_folder_data_model::entities::{ @@ -125,7 +125,7 @@ impl FolderTest { pub async fn run_script(&mut self, script: FolderScript) { let sdk = &self.sdk; - let folder_editor: Arc = sdk.folder_manager.folder_editor().await; + let folder_editor: Arc = sdk.folder_manager.folder_editor().await; let rev_manager = folder_editor.rev_manager(); let cache = rev_manager.revision_cache().await; diff --git a/frontend/rust-lib/flowy-grid/Cargo.toml b/frontend/rust-lib/flowy-grid/Cargo.toml index 1691b3eee7..43b0cbf69f 100644 --- a/frontend/rust-lib/flowy-grid/Cargo.toml +++ b/frontend/rust-lib/flowy-grid/Cargo.toml @@ -35,6 +35,8 @@ serde = { version = "1.0", features = ["derive"] } serde_json = {version = "1.0"} serde_repr = "0.1" indexmap = {version = "1.8.1", features = ["serde"]} +fancy-regex = "0.10.0" +url = { version = "2"} [dev-dependencies] flowy-test = { path = "../flowy-test" } diff --git a/frontend/rust-lib/flowy-grid/Flowy.toml b/frontend/rust-lib/flowy-grid/Flowy.toml index 1d0d2baea8..835988d41e 100644 --- a/frontend/rust-lib/flowy-grid/Flowy.toml +++ b/frontend/rust-lib/flowy-grid/Flowy.toml @@ -2,7 +2,7 @@ proto_crates = [ "src/event_map.rs", "src/services/field/type_options", - "src/services/entities", + "src/entities", "src/dart_notification.rs" ] event_files = ["src/event_map.rs"] \ No newline at end of file diff --git a/frontend/rust-lib/flowy-grid/src/services/entities/cell_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs similarity index 96% rename from frontend/rust-lib/flowy-grid/src/services/entities/cell_entities.rs rename to frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs index 2f408db992..4d09fe0eda 100644 --- a/frontend/rust-lib/flowy-grid/src/services/entities/cell_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs @@ -1,4 +1,4 @@ -use crate::services::entities::{FieldIdentifier, FieldIdentifierPayload}; +use crate::entities::{FieldIdentifier, FieldIdentifierPayload}; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; use flowy_grid_data_model::parser::NotEmptyStr; diff --git a/frontend/rust-lib/flowy-grid/src/services/entities/field_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs similarity index 100% rename from frontend/rust-lib/flowy-grid/src/services/entities/field_entities.rs rename to frontend/rust-lib/flowy-grid/src/entities/field_entities.rs diff --git a/frontend/rust-lib/flowy-grid/src/services/entities/mod.rs b/frontend/rust-lib/flowy-grid/src/entities/mod.rs similarity index 100% rename from frontend/rust-lib/flowy-grid/src/services/entities/mod.rs rename to frontend/rust-lib/flowy-grid/src/entities/mod.rs diff --git a/frontend/rust-lib/flowy-grid/src/services/entities/row_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/row_entities.rs similarity index 100% rename from frontend/rust-lib/flowy-grid/src/services/entities/row_entities.rs rename to frontend/rust-lib/flowy-grid/src/entities/row_entities.rs diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index 724d898d31..7374515793 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -1,5 +1,5 @@ +use crate::entities::*; use crate::manager::GridManager; -use crate::services::entities::*; use crate::services::field::type_options::*; use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_json_str}; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; @@ -7,7 +7,7 @@ use flowy_grid_data_model::entities::*; use lib_dispatch::prelude::{data_result, AppData, Data, DataResult}; use std::sync::Arc; -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_grid_data_handler( data: Data, manager: AppData>, @@ -34,7 +34,7 @@ pub(crate) async fn get_grid_blocks_handler( data_result(repeated_grid_block) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_fields_handler( data: Data, manager: AppData>, @@ -47,7 +47,7 @@ pub(crate) async fn get_fields_handler( data_result(repeated_field) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn update_field_handler( data: Data, manager: AppData>, @@ -58,7 +58,7 @@ pub(crate) async fn update_field_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn insert_field_handler( data: Data, manager: AppData>, @@ -69,7 +69,7 @@ pub(crate) async fn insert_field_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn update_field_type_option_handler( data: Data, manager: AppData>, @@ -82,7 +82,7 @@ pub(crate) async fn update_field_type_option_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn delete_field_handler( data: Data, manager: AppData>, @@ -93,7 +93,7 @@ pub(crate) async fn delete_field_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn switch_to_field_handler( data: Data, manager: AppData>, @@ -120,7 +120,7 @@ pub(crate) async fn switch_to_field_handler( data_result(data) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn duplicate_field_handler( data: Data, manager: AppData>, @@ -132,7 +132,7 @@ pub(crate) async fn duplicate_field_handler( } /// Return the FieldTypeOptionData if the Field exists otherwise return record not found error. -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_field_type_option_data_handler( data: Data, manager: AppData>, @@ -154,7 +154,7 @@ pub(crate) async fn get_field_type_option_data_handler( } /// Create FieldMeta and save it. Return the FieldTypeOptionData. -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn create_field_type_option_data_handler( data: Data, manager: AppData>, @@ -171,7 +171,7 @@ pub(crate) async fn create_field_type_option_data_handler( }) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn move_item_handler( data: Data, manager: AppData>, @@ -252,7 +252,7 @@ pub(crate) async fn get_cell_handler( } } -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_cell_handler( data: Data, manager: AppData>, @@ -263,28 +263,7 @@ pub(crate) async fn update_cell_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] -pub(crate) async fn get_date_cell_data_handler( - data: Data, - manager: AppData>, -) -> DataResult { - let params: CellIdentifier = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; - match editor.get_field_meta(¶ms.field_id).await { - None => { - tracing::error!("Can't find the corresponding field with id: {}", params.field_id); - data_result(DateCellData::default()) - } - Some(field_meta) => { - let cell_meta = editor.get_cell_meta(¶ms.row_id, ¶ms.field_id).await?; - let type_option = DateTypeOption::from(&field_meta); - let date_cell_data = type_option.make_date_cell_data(&cell_meta)?; - data_result(date_cell_data) - } - } -} - -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn new_select_option_handler( data: Data, manager: AppData>, @@ -301,7 +280,7 @@ pub(crate) async fn new_select_option_handler( } } -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_select_option_handler( data: Data, manager: AppData>, @@ -341,7 +320,7 @@ pub(crate) async fn update_select_option_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_select_option_handler( data: Data, manager: AppData>, @@ -350,7 +329,7 @@ pub(crate) async fn get_select_option_handler( let editor = manager.get_grid_editor(¶ms.grid_id)?; match editor.get_field_meta(¶ms.field_id).await { None => { - tracing::error!("Can't find the corresponding field with id: {}", params.field_id); + tracing::error!("Can't find the select option field with id: {}", params.field_id); data_result(SelectOptionCellData::default()) } Some(field_meta) => { @@ -362,7 +341,7 @@ pub(crate) async fn get_select_option_handler( } } -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_select_option_cell_handler( data: Data, manager: AppData>, @@ -373,7 +352,7 @@ pub(crate) async fn update_select_option_cell_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_date_cell_handler( data: Data, manager: AppData>, diff --git a/frontend/rust-lib/flowy-grid/src/event_map.rs b/frontend/rust-lib/flowy-grid/src/event_map.rs index c740eee6d8..48e5fd9b6c 100644 --- a/frontend/rust-lib/flowy-grid/src/event_map.rs +++ b/frontend/rust-lib/flowy-grid/src/event_map.rs @@ -29,7 +29,6 @@ pub fn create(grid_manager: Arc) -> Module { // Cell .event(GridEvent::GetCell, get_cell_handler) .event(GridEvent::UpdateCell, update_cell_handler) - .event(GridEvent::GetDateCellData, get_date_cell_data_handler) // SelectOption .event(GridEvent::NewSelectOption, new_select_option_handler) .event(GridEvent::UpdateSelectOption, update_select_option_handler) @@ -112,7 +111,4 @@ pub enum GridEvent { #[event(input = "DateChangesetPayload")] UpdateDateCell = 80, - - #[event(input = "CellIdentifierPayload", output = "DateCellData")] - GetDateCellData = 90, } diff --git a/frontend/rust-lib/flowy-grid/src/lib.rs b/frontend/rust-lib/flowy-grid/src/lib.rs index a3ac3411e2..4f7605b0b8 100644 --- a/frontend/rust-lib/flowy-grid/src/lib.rs +++ b/frontend/rust-lib/flowy-grid/src/lib.rs @@ -6,6 +6,7 @@ pub mod event_map; pub mod manager; mod dart_notification; +pub mod entities; mod protobuf; pub mod services; pub mod util; diff --git a/frontend/rust-lib/flowy-grid/src/manager.rs b/frontend/rust-lib/flowy-grid/src/manager.rs index 218dff8782..f8ea9e70ae 100644 --- a/frontend/rust-lib/flowy-grid/src/manager.rs +++ b/frontend/rust-lib/flowy-grid/src/manager.rs @@ -1,5 +1,5 @@ -use crate::services::grid_editor::ClientGridEditor; -use crate::services::persistence::block_index::BlockIndexPersistence; +use crate::services::grid_editor::GridMetaEditor; +use crate::services::persistence::block_index::BlockIndexCache; use crate::services::persistence::kv::GridKVPersistence; use crate::services::persistence::GridDatabase; use bytes::Bytes; @@ -20,9 +20,9 @@ pub trait GridUser: Send + Sync { } pub struct GridManager { - editor_map: Arc, + editor_map: Arc>>, grid_user: Arc, - block_index_persistence: Arc, + block_index_cache: Arc, #[allow(dead_code)] kv_persistence: Arc, } @@ -33,14 +33,14 @@ impl GridManager { _rev_web_socket: Arc, database: Arc, ) -> Self { - let grid_editors = Arc::new(GridEditorMap::new()); + let grid_editors = Arc::new(DashMap::new()); let kv_persistence = Arc::new(GridKVPersistence::new(database.clone())); - let block_index_persistence = Arc::new(BlockIndexPersistence::new(database)); + let block_index_persistence = Arc::new(BlockIndexCache::new(database)); Self { editor_map: grid_editors, grid_user, kv_persistence, - block_index_persistence, + block_index_cache: block_index_persistence, } } @@ -67,7 +67,7 @@ impl GridManager { } #[tracing::instrument(level = "debug", skip_all, fields(grid_id), err)] - pub async fn open_grid>(&self, grid_id: T) -> FlowyResult> { + pub async fn open_grid>(&self, grid_id: T) -> FlowyResult> { let grid_id = grid_id.as_ref(); tracing::Span::current().record("grid_id", &grid_id); self.get_or_create_grid_editor(grid_id).await @@ -90,23 +90,27 @@ impl GridManager { } // #[tracing::instrument(level = "debug", skip(self), err)] - pub fn get_grid_editor(&self, grid_id: &str) -> FlowyResult> { + pub fn get_grid_editor(&self, grid_id: &str) -> FlowyResult> { match self.editor_map.get(grid_id) { None => Err(FlowyError::internal().context("Should call open_grid function first")), - Some(editor) => Ok(editor), + Some(editor) => Ok(editor.clone()), } } - async fn get_or_create_grid_editor(&self, grid_id: &str) -> FlowyResult> { + async fn get_or_create_grid_editor(&self, grid_id: &str) -> FlowyResult> { match self.editor_map.get(grid_id) { None => { tracing::trace!("Create grid editor with id: {}", grid_id); let db_pool = self.grid_user.db_pool()?; let editor = self.make_grid_editor(grid_id, db_pool).await?; - self.editor_map.insert(grid_id, &editor); + + if self.editor_map.contains_key(grid_id) { + tracing::warn!("Grid:{} already exists in cache", grid_id); + } + self.editor_map.insert(grid_id.to_string(), editor.clone()); Ok(editor) } - Some(editor) => Ok(editor), + Some(editor) => Ok(editor.clone()), } } @@ -115,11 +119,10 @@ impl GridManager { &self, grid_id: &str, pool: Arc, - ) -> Result, FlowyError> { + ) -> Result, FlowyError> { let user = self.grid_user.clone(); let rev_manager = self.make_grid_rev_manager(grid_id, pool.clone())?; - let grid_editor = - ClientGridEditor::new(grid_id, user, rev_manager, self.block_index_persistence.clone()).await?; + let grid_editor = GridMetaEditor::new(grid_id, user, rev_manager, self.block_index_cache.clone()).await?; Ok(grid_editor) } @@ -145,31 +148,6 @@ impl GridManager { } } -pub struct GridEditorMap { - inner: DashMap>, -} - -impl GridEditorMap { - fn new() -> Self { - Self { inner: DashMap::new() } - } - - pub(crate) fn insert(&self, grid_id: &str, grid_editor: &Arc) { - if self.inner.contains_key(grid_id) { - tracing::warn!("Grid:{} already exists in cache", grid_id); - } - self.inner.insert(grid_id.to_string(), grid_editor.clone()); - } - - pub(crate) fn get(&self, grid_id: &str) -> Option> { - Some(self.inner.get(grid_id)?.clone()) - } - - pub(crate) fn remove(&self, grid_id: &str) { - self.inner.remove(grid_id); - } -} - pub async fn make_grid_view_data( user_id: &str, view_id: &str, @@ -192,9 +170,7 @@ pub async fn make_grid_view_data( // Indexing the block's rows build_context.block_meta_data.rows.iter().for_each(|row| { - let _ = grid_manager - .block_index_persistence - .insert_or_update(&row.block_id, &row.id); + let _ = grid_manager.block_index_cache.insert(&row.block_id, &row.id); }); // Create grid's block diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs index 6762254df7..73eaff920f 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs +++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs @@ -48,7 +48,6 @@ pub enum GridEvent { UpdateCell = 71, UpdateSelectOptionCell = 72, UpdateDateCell = 80, - GetDateCellData = 90, } impl ::protobuf::ProtobufEnum for GridEvent { @@ -81,7 +80,6 @@ impl ::protobuf::ProtobufEnum for GridEvent { 71 => ::std::option::Option::Some(GridEvent::UpdateCell), 72 => ::std::option::Option::Some(GridEvent::UpdateSelectOptionCell), 80 => ::std::option::Option::Some(GridEvent::UpdateDateCell), - 90 => ::std::option::Option::Some(GridEvent::GetDateCellData), _ => ::std::option::Option::None } } @@ -111,7 +109,6 @@ impl ::protobuf::ProtobufEnum for GridEvent { GridEvent::UpdateCell, GridEvent::UpdateSelectOptionCell, GridEvent::UpdateDateCell, - GridEvent::GetDateCellData, ]; values } @@ -140,7 +137,7 @@ impl ::protobuf::reflect::ProtobufValue for GridEvent { } static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x0fevent_map.proto*\xdc\x03\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\ + \n\x0fevent_map.proto*\xc7\x03\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\ \0\x12\x11\n\rGetGridBlocks\x10\x01\x12\r\n\tGetFields\x10\n\x12\x0f\n\ \x0bUpdateField\x10\x0b\x12\x19\n\x15UpdateFieldTypeOption\x10\x0c\x12\ \x0f\n\x0bInsertField\x10\r\x12\x0f\n\x0bDeleteField\x10\x0e\x12\x11\n\r\ @@ -151,7 +148,7 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x10\x20\x12\r\n\tCreateRow\x102\x12\n\n\x06GetRow\x103\x12\r\n\tDeleteR\ ow\x104\x12\x10\n\x0cDuplicateRow\x105\x12\x0b\n\x07GetCell\x10F\x12\x0e\ \n\nUpdateCell\x10G\x12\x1a\n\x16UpdateSelectOptionCell\x10H\x12\x12\n\ - \x0eUpdateDateCell\x10P\x12\x13\n\x0fGetDateCellData\x10Zb\x06proto3\ + \x0eUpdateDateCell\x10Pb\x06proto3\ "; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs index 99d0ecd1b6..c0f74e1e9c 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs @@ -19,6 +19,9 @@ pub use row_entities::*; mod cell_entities; pub use cell_entities::*; +mod url_type_option; +pub use url_type_option::*; + mod checkbox_type_option; pub use checkbox_type_option::*; diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/model/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/text_type_option.rs index febc180e03..b6bb5e55ab 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/model/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/text_type_option.rs @@ -26,7 +26,7 @@ #[derive(PartialEq,Clone,Default)] pub struct RichTextTypeOption { // message fields - pub format: ::std::string::String, + pub data: ::std::string::String, // special fields pub unknown_fields: ::protobuf::UnknownFields, pub cached_size: ::protobuf::CachedSize, @@ -43,30 +43,30 @@ impl RichTextTypeOption { ::std::default::Default::default() } - // string format = 1; + // string data = 1; - pub fn get_format(&self) -> &str { - &self.format + pub fn get_data(&self) -> &str { + &self.data } - pub fn clear_format(&mut self) { - self.format.clear(); + pub fn clear_data(&mut self) { + self.data.clear(); } // Param is passed by value, moved - pub fn set_format(&mut self, v: ::std::string::String) { - self.format = v; + pub fn set_data(&mut self, v: ::std::string::String) { + self.data = v; } // Mutable pointer to the field. // If field is not initialized, it is initialized with default value first. - pub fn mut_format(&mut self) -> &mut ::std::string::String { - &mut self.format + pub fn mut_data(&mut self) -> &mut ::std::string::String { + &mut self.data } // Take field - pub fn take_format(&mut self) -> ::std::string::String { - ::std::mem::replace(&mut self.format, ::std::string::String::new()) + pub fn take_data(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.data, ::std::string::String::new()) } } @@ -80,7 +80,7 @@ impl ::protobuf::Message for RichTextTypeOption { let (field_number, wire_type) = is.read_tag_unpack()?; match field_number { 1 => { - ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.format)?; + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?; }, _ => { ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; @@ -94,8 +94,8 @@ impl ::protobuf::Message for RichTextTypeOption { #[allow(unused_variables)] fn compute_size(&self) -> u32 { let mut my_size = 0; - if !self.format.is_empty() { - my_size += ::protobuf::rt::string_size(1, &self.format); + if !self.data.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.data); } my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); self.cached_size.set(my_size); @@ -103,8 +103,8 @@ impl ::protobuf::Message for RichTextTypeOption { } fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> { - if !self.format.is_empty() { - os.write_string(1, &self.format)?; + if !self.data.is_empty() { + os.write_string(1, &self.data)?; } os.write_unknown_fields(self.get_unknown_fields())?; ::std::result::Result::Ok(()) @@ -145,9 +145,9 @@ impl ::protobuf::Message for RichTextTypeOption { descriptor.get(|| { let mut fields = ::std::vec::Vec::new(); fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( - "format", - |m: &RichTextTypeOption| { &m.format }, - |m: &mut RichTextTypeOption| { &mut m.format }, + "data", + |m: &RichTextTypeOption| { &m.data }, + |m: &mut RichTextTypeOption| { &mut m.data }, )); ::protobuf::reflect::MessageDescriptor::new_pb_name::( "RichTextTypeOption", @@ -165,7 +165,7 @@ impl ::protobuf::Message for RichTextTypeOption { impl ::protobuf::Clear for RichTextTypeOption { fn clear(&mut self) { - self.format.clear(); + self.data.clear(); self.unknown_fields.clear(); } } @@ -183,8 +183,8 @@ impl ::protobuf::reflect::ProtobufValue for RichTextTypeOption { } static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x16text_type_option.proto\",\n\x12RichTextTypeOption\x12\x16\n\x06for\ - mat\x18\x01\x20\x01(\tR\x06formatb\x06proto3\ + \n\x16text_type_option.proto\"(\n\x12RichTextTypeOption\x12\x12\n\x04dat\ + a\x18\x01\x20\x01(\tR\x04datab\x06proto3\ "; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/model/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/url_type_option.rs new file mode 100644 index 0000000000..fe83999fd3 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/url_type_option.rs @@ -0,0 +1,403 @@ +// This file is generated by rust-protobuf 2.25.2. Do not edit +// @generated + +// https://github.com/rust-lang/rust-clippy/issues/702 +#![allow(unknown_lints)] +#![allow(clippy::all)] + +#![allow(unused_attributes)] +#![cfg_attr(rustfmt, rustfmt::skip)] + +#![allow(box_pointers)] +#![allow(dead_code)] +#![allow(missing_docs)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(trivial_casts)] +#![allow(unused_imports)] +#![allow(unused_results)] +//! Generated file from `url_type_option.proto` + +/// Generated files are compatible only with the same version +/// of protobuf runtime. +// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2; + +#[derive(PartialEq,Clone,Default)] +pub struct URLTypeOption { + // message fields + pub data: ::std::string::String, + // special fields + pub unknown_fields: ::protobuf::UnknownFields, + pub cached_size: ::protobuf::CachedSize, +} + +impl<'a> ::std::default::Default for &'a URLTypeOption { + fn default() -> &'a URLTypeOption { + ::default_instance() + } +} + +impl URLTypeOption { + pub fn new() -> URLTypeOption { + ::std::default::Default::default() + } + + // string data = 1; + + + pub fn get_data(&self) -> &str { + &self.data + } + pub fn clear_data(&mut self) { + self.data.clear(); + } + + // Param is passed by value, moved + pub fn set_data(&mut self, v: ::std::string::String) { + self.data = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_data(&mut self) -> &mut ::std::string::String { + &mut self.data + } + + // Take field + pub fn take_data(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.data, ::std::string::String::new()) + } +} + +impl ::protobuf::Message for URLTypeOption { + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> { + while !is.eof()? { + let (field_number, wire_type) = is.read_tag_unpack()?; + match field_number { + 1 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?; + }, + _ => { + ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u32 { + let mut my_size = 0; + if !self.data.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.data); + } + my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); + self.cached_size.set(my_size); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> { + if !self.data.is_empty() { + os.write_string(1, &self.data)?; + } + os.write_unknown_fields(self.get_unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn get_cached_size(&self) -> u32 { + self.cached_size.get() + } + + fn get_unknown_fields(&self) -> &::protobuf::UnknownFields { + &self.unknown_fields + } + + fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields { + &mut self.unknown_fields + } + + fn as_any(&self) -> &dyn (::std::any::Any) { + self as &dyn (::std::any::Any) + } + fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) { + self as &mut dyn (::std::any::Any) + } + fn into_any(self: ::std::boxed::Box) -> ::std::boxed::Box { + self + } + + fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor { + Self::descriptor_static() + } + + fn new() -> URLTypeOption { + URLTypeOption::new() + } + + fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT; + descriptor.get(|| { + let mut fields = ::std::vec::Vec::new(); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "data", + |m: &URLTypeOption| { &m.data }, + |m: &mut URLTypeOption| { &mut m.data }, + )); + ::protobuf::reflect::MessageDescriptor::new_pb_name::( + "URLTypeOption", + fields, + file_descriptor_proto() + ) + }) + } + + fn default_instance() -> &'static URLTypeOption { + static instance: ::protobuf::rt::LazyV2 = ::protobuf::rt::LazyV2::INIT; + instance.get(URLTypeOption::new) + } +} + +impl ::protobuf::Clear for URLTypeOption { + fn clear(&mut self) { + self.data.clear(); + self.unknown_fields.clear(); + } +} + +impl ::std::fmt::Debug for URLTypeOption { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for URLTypeOption { + fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef { + ::protobuf::reflect::ReflectValueRef::Message(self) + } +} + +#[derive(PartialEq,Clone,Default)] +pub struct URLCellData { + // message fields + pub url: ::std::string::String, + pub content: ::std::string::String, + // special fields + pub unknown_fields: ::protobuf::UnknownFields, + pub cached_size: ::protobuf::CachedSize, +} + +impl<'a> ::std::default::Default for &'a URLCellData { + fn default() -> &'a URLCellData { + ::default_instance() + } +} + +impl URLCellData { + pub fn new() -> URLCellData { + ::std::default::Default::default() + } + + // string url = 1; + + + pub fn get_url(&self) -> &str { + &self.url + } + pub fn clear_url(&mut self) { + self.url.clear(); + } + + // Param is passed by value, moved + pub fn set_url(&mut self, v: ::std::string::String) { + self.url = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_url(&mut self) -> &mut ::std::string::String { + &mut self.url + } + + // Take field + pub fn take_url(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.url, ::std::string::String::new()) + } + + // string content = 2; + + + pub fn get_content(&self) -> &str { + &self.content + } + pub fn clear_content(&mut self) { + self.content.clear(); + } + + // Param is passed by value, moved + pub fn set_content(&mut self, v: ::std::string::String) { + self.content = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_content(&mut self) -> &mut ::std::string::String { + &mut self.content + } + + // Take field + pub fn take_content(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.content, ::std::string::String::new()) + } +} + +impl ::protobuf::Message for URLCellData { + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> { + while !is.eof()? { + let (field_number, wire_type) = is.read_tag_unpack()?; + match field_number { + 1 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.url)?; + }, + 2 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.content)?; + }, + _ => { + ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u32 { + let mut my_size = 0; + if !self.url.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.url); + } + if !self.content.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.content); + } + my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); + self.cached_size.set(my_size); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> { + if !self.url.is_empty() { + os.write_string(1, &self.url)?; + } + if !self.content.is_empty() { + os.write_string(2, &self.content)?; + } + os.write_unknown_fields(self.get_unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn get_cached_size(&self) -> u32 { + self.cached_size.get() + } + + fn get_unknown_fields(&self) -> &::protobuf::UnknownFields { + &self.unknown_fields + } + + fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields { + &mut self.unknown_fields + } + + fn as_any(&self) -> &dyn (::std::any::Any) { + self as &dyn (::std::any::Any) + } + fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) { + self as &mut dyn (::std::any::Any) + } + fn into_any(self: ::std::boxed::Box) -> ::std::boxed::Box { + self + } + + fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor { + Self::descriptor_static() + } + + fn new() -> URLCellData { + URLCellData::new() + } + + fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT; + descriptor.get(|| { + let mut fields = ::std::vec::Vec::new(); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "url", + |m: &URLCellData| { &m.url }, + |m: &mut URLCellData| { &mut m.url }, + )); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "content", + |m: &URLCellData| { &m.content }, + |m: &mut URLCellData| { &mut m.content }, + )); + ::protobuf::reflect::MessageDescriptor::new_pb_name::( + "URLCellData", + fields, + file_descriptor_proto() + ) + }) + } + + fn default_instance() -> &'static URLCellData { + static instance: ::protobuf::rt::LazyV2 = ::protobuf::rt::LazyV2::INIT; + instance.get(URLCellData::new) + } +} + +impl ::protobuf::Clear for URLCellData { + fn clear(&mut self) { + self.url.clear(); + self.content.clear(); + self.unknown_fields.clear(); + } +} + +impl ::std::fmt::Debug for URLCellData { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for URLCellData { + fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef { + ::protobuf::reflect::ReflectValueRef::Message(self) + } +} + +static file_descriptor_proto_data: &'static [u8] = b"\ + \n\x15url_type_option.proto\"#\n\rURLTypeOption\x12\x12\n\x04data\x18\ + \x01\x20\x01(\tR\x04data\"9\n\x0bURLCellData\x12\x10\n\x03url\x18\x01\ + \x20\x01(\tR\x03url\x12\x18\n\x07content\x18\x02\x20\x01(\tR\x07contentb\ + \x06proto3\ +"; + +static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; + +fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto { + ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap() +} + +pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto { + file_descriptor_proto_lazy.get(|| { + parse_descriptor_proto() + }) +} diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto b/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto index 10e98a2934..623bf4b7da 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto +++ b/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto @@ -24,5 +24,4 @@ enum GridEvent { UpdateCell = 71; UpdateSelectOptionCell = 72; UpdateDateCell = 80; - GetDateCellData = 90; } diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/proto/text_type_option.proto b/frontend/rust-lib/flowy-grid/src/protobuf/proto/text_type_option.proto index 67cfb438ea..827c569a74 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/proto/text_type_option.proto +++ b/frontend/rust-lib/flowy-grid/src/protobuf/proto/text_type_option.proto @@ -1,5 +1,5 @@ syntax = "proto3"; message RichTextTypeOption { - string format = 1; + string data = 1; } diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/proto/url_type_option.proto b/frontend/rust-lib/flowy-grid/src/protobuf/proto/url_type_option.proto new file mode 100644 index 0000000000..edf1c7e341 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/protobuf/proto/url_type_option.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +message URLTypeOption { + string data = 1; +} +message URLCellData { + string url = 1; + string content = 2; +} diff --git a/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs b/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs index ce7c5c8e8d..d1cb847b37 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs @@ -8,18 +8,17 @@ use flowy_sync::util::make_delta_from_revisions; use lib_infra::future::FutureResult; use lib_ot::core::PlainTextAttributes; use std::borrow::Cow; - use std::sync::Arc; use tokio::sync::RwLock; -pub struct ClientGridBlockMetaEditor { +pub struct GridBlockMetaEditor { user_id: String, pub block_id: String, pad: Arc>, rev_manager: Arc, } -impl ClientGridBlockMetaEditor { +impl GridBlockMetaEditor { pub async fn new( user_id: &str, token: &str, diff --git a/frontend/rust-lib/flowy-grid/src/services/block_meta_manager.rs b/frontend/rust-lib/flowy-grid/src/services/block_meta_manager.rs index 229afe75a2..6ce36488c2 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_meta_manager.rs +++ b/frontend/rust-lib/flowy-grid/src/services/block_meta_manager.rs @@ -1,7 +1,7 @@ use crate::dart_notification::{send_dart_notification, GridNotification}; use crate::manager::GridUser; -use crate::services::block_meta_editor::ClientGridBlockMetaEditor; -use crate::services::persistence::block_index::BlockIndexPersistence; +use crate::services::block_meta_editor::GridBlockMetaEditor; +use crate::services::persistence::block_index::BlockIndexCache; use crate::services::row::{group_row_orders, GridBlockSnapshot}; use dashmap::DashMap; use flowy_error::FlowyResult; @@ -15,20 +15,20 @@ use std::borrow::Cow; use std::collections::HashMap; use std::sync::Arc; -pub(crate) struct GridBlockMetaEditorManager { +type BlockId = String; +pub(crate) struct GridBlockManager { grid_id: String, user: Arc, - // Key: block_id - editor_map: DashMap>, - persistence: Arc, + persistence: Arc, + block_editor_map: DashMap>, } -impl GridBlockMetaEditorManager { +impl GridBlockManager { pub(crate) async fn new( grid_id: &str, user: &Arc, blocks: Vec, - persistence: Arc, + persistence: Arc, ) -> FlowyResult { let editor_map = make_block_meta_editor_map(user, blocks).await?; let user = user.clone(); @@ -36,27 +36,27 @@ impl GridBlockMetaEditorManager { let manager = Self { grid_id, user, - editor_map, + block_editor_map: editor_map, persistence, }; Ok(manager) } // #[tracing::instrument(level = "trace", skip(self))] - pub(crate) async fn get_editor(&self, block_id: &str) -> FlowyResult> { + pub(crate) async fn get_editor(&self, block_id: &str) -> FlowyResult> { debug_assert!(!block_id.is_empty()); - match self.editor_map.get(block_id) { + match self.block_editor_map.get(block_id) { None => { tracing::error!("The is a fatal error, block is not exist"); let editor = Arc::new(make_block_meta_editor(&self.user, block_id).await?); - self.editor_map.insert(block_id.to_owned(), editor.clone()); + self.block_editor_map.insert(block_id.to_owned(), editor.clone()); Ok(editor) } Some(editor) => Ok(editor.clone()), } } - async fn get_editor_from_row_id(&self, row_id: &str) -> FlowyResult> { + async fn get_editor_from_row_id(&self, row_id: &str) -> FlowyResult> { let block_id = self.persistence.get_block_id(row_id)?; Ok(self.get_editor(&block_id).await?) } @@ -67,7 +67,7 @@ impl GridBlockMetaEditorManager { row_meta: RowMeta, start_row_id: Option, ) -> FlowyResult { - let _ = self.persistence.insert_or_update(&row_meta.block_id, &row_meta.id)?; + let _ = self.persistence.insert(&row_meta.block_id, &row_meta.id)?; let editor = self.get_editor(&row_meta.block_id).await?; let mut index_row_order = IndexRowOrder::from(&row_meta); @@ -90,7 +90,7 @@ impl GridBlockMetaEditorManager { let editor = self.get_editor(&block_id).await?; let mut row_count = 0; for row in row_metas { - let _ = self.persistence.insert_or_update(&row.block_id, &row.id)?; + let _ = self.persistence.insert(&row.block_id, &row.id)?; let mut row_order = IndexRowOrder::from(&row); let (count, index) = editor.create_row(row, None).await?; row_count = count; @@ -256,7 +256,7 @@ impl GridBlockMetaEditorManager { async fn make_block_meta_editor_map( user: &Arc, blocks: Vec, -) -> FlowyResult>> { +) -> FlowyResult>> { let editor_map = DashMap::new(); for block in blocks { let editor = make_block_meta_editor(user, &block.block_id).await?; @@ -266,7 +266,7 @@ async fn make_block_meta_editor_map( Ok(editor_map) } -async fn make_block_meta_editor(user: &Arc, block_id: &str) -> FlowyResult { +async fn make_block_meta_editor(user: &Arc, block_id: &str) -> FlowyResult { let token = user.token()?; let user_id = user.user_id()?; let pool = user.db_pool()?; @@ -274,5 +274,5 @@ async fn make_block_meta_editor(user: &Arc, block_id: &str) -> Flo let disk_cache = Arc::new(SQLiteGridBlockMetaRevisionPersistence::new(&user_id, pool)); let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, block_id, disk_cache)); let rev_manager = RevisionManager::new(&user_id, block_id, rev_persistence); - ClientGridBlockMetaEditor::new(&user_id, &token, block_id, rev_manager).await + GridBlockMetaEditor::new(&user_id, &token, block_id, rev_manager).await } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs index 5eaabb0294..7978323be1 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs @@ -94,6 +94,7 @@ pub fn default_type_option_builder_from_type(field_type: &FieldType) -> Box SingleSelectTypeOption::default().into(), FieldType::MultiSelect => MultiSelectTypeOption::default().into(), FieldType::Checkbox => CheckboxTypeOption::default().into(), + FieldType::URL => URLTypeOption::default().into(), }; type_option_builder_from_json_str(&s, field_type) @@ -107,6 +108,7 @@ pub fn type_option_builder_from_json_str(s: &str, field_type: &FieldType) -> Box FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_json_str(s)), FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_json_str(s)), FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_json_str(s)), + FieldType::URL => Box::new(URLTypeOptionBuilder::from_json_str(s)), } } @@ -119,5 +121,6 @@ pub fn type_option_builder_from_bytes>(bytes: T, field_type: &Fie FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_protobuf_bytes(bytes)), FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_protobuf_bytes(bytes)), FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_protobuf_bytes(bytes)), + FieldType::URL => Box::new(URLTypeOptionBuilder::from_protobuf_bytes(bytes)), } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs index 73c3f5f2d7..c8d14b6b03 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs @@ -1,15 +1,14 @@ use crate::impl_type_option; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData}; +use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData}; use bytes::Bytes; use flowy_derive::ProtoBuf; -use flowy_error::FlowyError; +use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{ CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, }; use serde::{Deserialize, Serialize}; -use std::str::FromStr; #[derive(Default)] pub struct CheckboxTypeOptionBuilder(CheckboxTypeOption); @@ -43,32 +42,38 @@ impl_type_option!(CheckboxTypeOption, FieldType::Checkbox); const YES: &str = "Yes"; const NO: &str = "No"; -impl CellDataOperation for CheckboxTypeOption { - fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - if !type_option_cell_data.is_checkbox() { - return DecodedCellData::default(); - } - let cell_data = type_option_cell_data.data; - if cell_data == YES || cell_data == NO { - return DecodedCellData::from_content(cell_data); - } +impl CellDataOperation for CheckboxTypeOption { + fn decode_cell_data( + &self, + encoded_data: T, + decoded_field_type: &FieldType, + _field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into, + { + if !decoded_field_type.is_checkbox() { + return Ok(DecodedCellData::default()); } - DecodedCellData::default() + let encoded_data = encoded_data.into(); + if encoded_data == YES || encoded_data == NO { + return Ok(DecodedCellData::new(encoded_data)); + } + + Ok(DecodedCellData::default()) } - fn apply_changeset>( - &self, - changeset: T, - _cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { let changeset = changeset.into(); let s = match string_to_bool(&changeset) { true => YES, false => NO, }; - Ok(TypeOptionCellData::new(s, self.field_type()).json()) + Ok(s.to_string()) } } @@ -88,32 +93,49 @@ fn string_to_bool(bool_str: &str) -> bool { #[cfg(test)] mod tests { use crate::services::field::type_options::checkbox_type_option::{NO, YES}; - use crate::services::field::CheckboxTypeOption; + use crate::services::field::FieldBuilder; - use crate::services::row::CellDataOperation; + use crate::services::row::{apply_cell_data_changeset, decode_cell_data_from_type_option_cell_data}; + use flowy_grid_data_model::entities::FieldType; #[test] fn checkout_box_description_test() { - let type_option = CheckboxTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); + let field_meta = FieldBuilder::from_field_type(&FieldType::Checkbox).build(); + let data = apply_cell_data_changeset("true", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), + YES + ); - let data = type_option.apply_changeset("true", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES); + let data = apply_cell_data_changeset("1", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), + YES + ); - let data = type_option.apply_changeset("1", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES); + let data = apply_cell_data_changeset("yes", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), + YES + ); - let data = type_option.apply_changeset("yes", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES); + let data = apply_cell_data_changeset("false", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), + NO + ); - let data = type_option.apply_changeset("false", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO); + let data = apply_cell_data_changeset("no", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), + NO + ); - let data = type_option.apply_changeset("no", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO); - - let data = type_option.apply_changeset("123", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO); + let data = apply_cell_data_changeset("12", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), + NO + ); } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs index 84cfe0e4bf..c96166272a 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs @@ -1,7 +1,7 @@ +use crate::entities::{CellIdentifier, CellIdentifierPayload}; use crate::impl_type_option; -use crate::services::entities::{CellIdentifier, CellIdentifierPayload}; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData}; +use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, EncodedCellData}; use bytes::Bytes; use chrono::format::strftime::StrftimeItems; use chrono::NaiveDateTime; @@ -77,32 +77,12 @@ impl DateTypeOption { } } - pub fn make_date_cell_data(&self, cell_meta: &Option) -> FlowyResult { - if cell_meta.is_none() { - return Ok(DateCellData::default()); - } - - let json = &cell_meta.as_ref().unwrap().data; - let result = TypeOptionCellData::from_str(json); - if result.is_err() { - return Ok(DateCellData::default()); - } - - let serde_cell_data = DateCellDataSerde::from_str(&result.unwrap().data)?; - let date = self.decode_cell_data_from_timestamp(&serde_cell_data).content; - let time = serde_cell_data.time.unwrap_or("".to_owned()); - let timestamp = serde_cell_data.timestamp; - - return Ok(DateCellData { date, time, timestamp }); - } - - fn decode_cell_data_from_timestamp(&self, serde_cell_data: &DateCellDataSerde) -> DecodedCellData { + fn date_desc_from_timestamp(&self, serde_cell_data: &DateCellDataSerde) -> String { if serde_cell_data.timestamp == 0 { - return DecodedCellData::default(); + return "".to_owned(); } - let cell_content = self.today_desc_from_timestamp(serde_cell_data.timestamp, &serde_cell_data.time); - return DecodedCellData::new(serde_cell_data.timestamp.to_string(), cell_content); + self.today_desc_from_timestamp(serde_cell_data.timestamp, &serde_cell_data.time) } fn timestamp_from_utc_with_time( @@ -131,34 +111,40 @@ impl DateTypeOption { } } - return Ok(utc.timestamp()); + Ok(utc.timestamp()) } } -impl CellDataOperation for DateTypeOption { - fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - // Return default data if the type_option_cell_data is not FieldType::DateTime. - // It happens when switching from one field to another. - // For example: - // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen. - if !type_option_cell_data.is_date() { - return DecodedCellData::default(); - } - return match DateCellDataSerde::from_str(&type_option_cell_data.data) { - Ok(serde_cell_data) => self.decode_cell_data_from_timestamp(&serde_cell_data), - Err(_) => DecodedCellData::default(), - }; +impl CellDataOperation, DateCellDataSerde> for DateTypeOption { + fn decode_cell_data( + &self, + encoded_data: T, + decoded_field_type: &FieldType, + _field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into>, + { + // Return default data if the type_option_cell_data is not FieldType::DateTime. + // It happens when switching from one field to another. + // For example: + // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen. + if !decoded_field_type.is_date() { + return Ok(DecodedCellData::default()); } - DecodedCellData::default() + let encoded_data = encoded_data.into().try_into_inner()?; + let date = self.date_desc_from_timestamp(&encoded_data); + let time = encoded_data.time.unwrap_or_else(|| "".to_owned()); + let timestamp = encoded_data.timestamp; + + DecodedCellData::try_from_bytes(DateCellData { date, time, timestamp }) } - fn apply_changeset>( - &self, - changeset: T, - _cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { let content_changeset: DateCellContentChangeset = serde_json::from_str(&changeset.into())?; let cell_data = match content_changeset.date_timestamp() { None => DateCellDataSerde::default(), @@ -173,7 +159,7 @@ impl CellDataOperation for DateTypeOption { }, }; - Ok(TypeOptionCellData::new(cell_data.to_string(), self.field_type()).json()) + Ok(cell_data) } } @@ -307,23 +293,29 @@ impl DateCellDataSerde { fn new(timestamp: i64, time: Option, time_format: &TimeFormat) -> Self { Self { timestamp, - time: Some(time.unwrap_or(default_time_str(time_format))), + time: Some(time.unwrap_or_else(|| default_time_str(time_format))), } } pub(crate) fn from_timestamp(timestamp: i64, time: Option) -> Self { Self { timestamp, time } } +} - fn to_string(self) -> String { - serde_json::to_string(&self).unwrap_or("".to_string()) - } +impl FromStr for DateCellDataSerde { + type Err = FlowyError; - fn from_str(s: &str) -> FlowyResult { + fn from_str(s: &str) -> Result { serde_json::from_str::(s).map_err(internal_error) } } +impl ToString for DateCellDataSerde { + fn to_string(&self) -> String { + serde_json::to_string(&self).unwrap_or_else(|_| "".to_string()) + } +} + fn default_time_str(time_format: &TimeFormat) -> String { match time_format { TimeFormat::TwelveHour => "12:00 AM".to_string(), @@ -410,50 +402,45 @@ mod tests { use crate::services::field::{ DateCellContentChangeset, DateCellData, DateCellDataSerde, DateFormat, DateTypeOption, TimeFormat, }; - use crate::services::row::{CellDataOperation, TypeOptionCellData}; - use flowy_grid_data_model::entities::FieldType; + use crate::services::row::{CellDataOperation, EncodedCellData}; + use flowy_grid_data_model::entities::{FieldMeta, FieldType}; use strum::IntoEnumIterator; #[test] fn date_description_invalid_input_test() { let type_option = DateTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - assert_eq!( - "".to_owned(), - type_option.decode_cell_data("1e".to_owned(), &field_meta).content + let field_type = FieldType::DateTime; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some("1e".to_string()), + time: Some("23:00".to_owned()), + }, + &field_type, + &field_meta, + "", ); } #[test] fn date_description_date_format_test() { let mut type_option = DateTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); + let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); for date_format in DateFormat::iter() { type_option.date_format = date_format; match date_format { DateFormat::Friendly => { - assert_eq!( - "Mar 14,2022".to_owned(), - type_option.decode_cell_data(data(1647251762), &field_meta).content - ); + assert_decode_timestamp(1647251762, &type_option, &field_meta, "Mar 14,2022"); } DateFormat::US => { - assert_eq!( - "2022/03/14".to_owned(), - type_option.decode_cell_data(data(1647251762), &field_meta).content - ); + assert_decode_timestamp(1647251762, &type_option, &field_meta, "2022/03/14"); } DateFormat::ISO => { - assert_eq!( - "2022-03-14".to_owned(), - type_option.decode_cell_data(data(1647251762), &field_meta).content - ); + assert_decode_timestamp(1647251762, &type_option, &field_meta, "2022-03-14"); } DateFormat::Local => { - assert_eq!( - "2022/03/14".to_owned(), - type_option.decode_cell_data(data(1647251762), &field_meta).content - ); + assert_decode_timestamp(1647251762, &type_option, &field_meta, "2022/03/14"); } } } @@ -462,84 +449,68 @@ mod tests { #[test] fn date_description_time_format_test() { let mut type_option = DateTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - for time_format in TimeFormat::iter() { - type_option.time_format = time_format; - match time_format { - TimeFormat::TwentyFourHour => { - assert_eq!( - "Mar 14,2022".to_owned(), - type_option.today_desc_from_timestamp(1647251762, &None) - ); - assert_eq!( - "Mar 14,2022".to_owned(), - type_option.decode_cell_data(data(1647251762), &field_meta).content - ); - } - TimeFormat::TwelveHour => { - assert_eq!( - "Mar 14,2022".to_owned(), - type_option.today_desc_from_timestamp(1647251762, &None) - ); - assert_eq!( - "Mar 14,2022".to_owned(), - type_option.decode_cell_data(data(1647251762), &field_meta).content - ); - } - } - } - } + let field_type = FieldType::DateTime; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); - #[test] - fn date_description_time_format_test2() { - let mut type_option = DateTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); for time_format in TimeFormat::iter() { type_option.time_format = time_format; type_option.include_time = true; match time_format { TimeFormat::TwentyFourHour => { - let changeset = DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: None, - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!("May 27,2022 00:00".to_owned(), content); - - let changeset = DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: Some("23:00".to_owned()), - }; - - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!("May 27,2022 23:00".to_owned(), content); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(1653609600.to_string()), + time: None, + }, + &field_type, + &field_meta, + "May 27,2022 00:00", + ); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(1653609600.to_string()), + time: Some("23:00".to_owned()), + }, + &field_type, + &field_meta, + "May 27,2022 23:00", + ); } TimeFormat::TwelveHour => { - let changeset = DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: None, - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!("May 27,2022 12:00 AM".to_owned(), content); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(1653609600.to_string()), + time: None, + }, + &field_type, + &field_meta, + "May 27,2022 12:00 AM", + ); - let changeset = DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: Some("".to_owned()), - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!("May 27,2022".to_owned(), content); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(1653609600.to_string()), + time: Some("".to_owned()), + }, + &field_type, + &field_meta, + "May 27,2022", + ); - let changeset = DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: Some("11:23 pm".to_owned()), - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!("May 27,2022 11:23 PM".to_owned(), content); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(1653609600.to_string()), + time: Some("11:23 pm".to_owned()), + }, + &field_type, + &field_meta, + "May 27,2022 11:23 PM", + ); } } } @@ -548,37 +519,55 @@ mod tests { #[test] fn date_description_apply_changeset_test() { let mut type_option = DateTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); + let field_type = FieldType::DateTime; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); let date_timestamp = "1653609600".to_owned(); - let changeset = DateCellContentChangeset { - date: Some(date_timestamp.clone()), - time: None, - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result.clone(), &field_meta).content; - assert_eq!(content, "May 27,2022".to_owned()); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(date_timestamp.clone()), + time: None, + }, + &field_type, + &field_meta, + "May 27,2022", + ); type_option.include_time = true; - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!(content, "May 27,2022 00:00".to_owned()); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(date_timestamp.clone()), + time: None, + }, + &field_type, + &field_meta, + "May 27,2022 00:00", + ); - let changeset = DateCellContentChangeset { - date: Some(date_timestamp.clone()), - time: Some("1:00".to_owned()), - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!(content, "May 27,2022 01:00".to_owned()); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(date_timestamp.clone()), + time: Some("1:00".to_owned()), + }, + &field_type, + &field_meta, + "May 27,2022 01:00", + ); - let changeset = DateCellContentChangeset { - date: Some(date_timestamp), - time: Some("1:00 am".to_owned()), - }; type_option.time_format = TimeFormat::TwelveHour; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!(content, "May 27,2022 01:00 AM".to_owned()); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(date_timestamp), + time: Some("1:00 am".to_owned()), + }, + &field_type, + &field_meta, + "May 27,2022 01:00 AM", + ); } #[test] @@ -586,7 +575,7 @@ mod tests { fn date_description_apply_changeset_error_test() { let mut type_option = DateTypeOption::default(); type_option.include_time = true; - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); + let _field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); let date_timestamp = "1653609600".to_owned(); let changeset = DateCellContentChangeset { @@ -596,7 +585,7 @@ mod tests { let _ = type_option.apply_changeset(changeset, None).unwrap(); let changeset = DateCellContentChangeset { - date: Some(date_timestamp.clone()), + date: Some(date_timestamp), time: Some("1:".to_owned()), }; let _ = type_option.apply_changeset(changeset, None).unwrap(); @@ -609,8 +598,39 @@ mod tests { type_option.apply_changeset("he", None).unwrap(); } - fn data(s: i64) -> String { - let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(s, None)).unwrap(); - TypeOptionCellData::new(&json, FieldType::DateTime).json() + fn assert_changeset_result( + type_option: &DateTypeOption, + changeset: DateCellContentChangeset, + _field_type: &FieldType, + field_meta: &FieldMeta, + expected: &str, + ) { + let encoded_data = EncodedCellData(Some(type_option.apply_changeset(changeset, None).unwrap())); + assert_eq!( + expected.to_owned(), + decode_cell_data(encoded_data, type_option, field_meta) + ); + } + + fn assert_decode_timestamp(timestamp: i64, type_option: &DateTypeOption, field_meta: &FieldMeta, expected: &str) { + let serde_json = DateCellDataSerde { timestamp, time: None }.to_string(); + + assert_eq!( + expected.to_owned(), + decode_cell_data(serde_json, type_option, field_meta) + ); + } + + fn decode_cell_data>>( + encoded_data: T, + type_option: &DateTypeOption, + field_meta: &FieldMeta, + ) -> String { + type_option + .decode_cell_data(encoded_data, &FieldType::DateTime, field_meta) + .unwrap() + .parse::() + .unwrap() + .date } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs index 2c74b2097b..3cfe390b38 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs @@ -3,6 +3,7 @@ mod date_type_option; mod number_type_option; mod selection_type_option; mod text_type_option; +mod url_type_option; mod util; pub use checkbox_type_option::*; @@ -10,3 +11,4 @@ pub use date_type_option::*; pub use number_type_option::*; pub use selection_type_option::*; pub use text_type_option::*; +pub use url_type_option::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs index a57b056f5d..cf4bd22e3d 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs @@ -1,9 +1,9 @@ use crate::impl_type_option; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData}; +use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData}; use bytes::Bytes; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_error::FlowyError; +use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{ CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, }; @@ -76,45 +76,48 @@ pub struct NumberTypeOption { } impl_type_option!(NumberTypeOption, FieldType::Number); -impl CellDataOperation for NumberTypeOption { - fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - if type_option_cell_data.is_date() { - return DecodedCellData::default(); +impl CellDataOperation for NumberTypeOption { + fn decode_cell_data( + &self, + encoded_data: T, + decoded_field_type: &FieldType, + _field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into, + { + if decoded_field_type.is_date() { + return Ok(DecodedCellData::default()); + } + + let cell_data = encoded_data.into(); + match self.format { + NumberFormat::Number => { + if let Ok(v) = cell_data.parse::() { + return Ok(DecodedCellData::new(v.to_string())); + } + + if let Ok(v) = cell_data.parse::() { + return Ok(DecodedCellData::new(v.to_string())); + } + + Ok(DecodedCellData::default()) } - - let cell_data = type_option_cell_data.data; - match self.format { - NumberFormat::Number => { - if let Ok(v) = cell_data.parse::() { - return DecodedCellData::from_content(v.to_string()); - } - - if let Ok(v) = cell_data.parse::() { - return DecodedCellData::from_content(v.to_string()); - } - - DecodedCellData::default() - } - NumberFormat::Percent => { - let content = cell_data.parse::().map_or(String::new(), |v| v.to_string()); - DecodedCellData::from_content(content) - } - _ => { - let content = self.money_from_str(&cell_data); - DecodedCellData::from_content(content) - } + NumberFormat::Percent => { + let content = cell_data.parse::().map_or(String::new(), |v| v.to_string()); + Ok(DecodedCellData::new(content)) + } + _ => { + let content = self.money_from_str(&cell_data); + Ok(DecodedCellData::new(content)) } - } else { - DecodedCellData::default() } } - fn apply_changeset>( - &self, - changeset: T, - _cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { let changeset = changeset.into(); let mut data = changeset.trim().to_string(); @@ -125,7 +128,7 @@ impl CellDataOperation for NumberTypeOption { } } - Ok(TypeOptionCellData::new(&data, self.field_type()).json()) + Ok(data) } } @@ -619,28 +622,24 @@ fn make_strip_symbol() -> Vec { mod tests { use crate::services::field::FieldBuilder; use crate::services::field::{NumberFormat, NumberTypeOption}; - use crate::services::row::{CellDataOperation, TypeOptionCellData}; - use flowy_grid_data_model::entities::FieldType; + use crate::services::row::CellDataOperation; + use flowy_grid_data_model::entities::{FieldMeta, FieldType}; use strum::IntoEnumIterator; #[test] fn number_description_invalid_input_test() { let type_option = NumberTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - assert_eq!( - "".to_owned(), - type_option.decode_cell_data(data(""), &field_meta).content - ); - assert_eq!( - "".to_owned(), - type_option.decode_cell_data(data("abc"), &field_meta).content - ); + let field_type = FieldType::Number; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); + assert_equal(&type_option, "", "", &field_type, &field_meta); + assert_equal(&type_option, "abc", "", &field_type, &field_meta); } #[test] fn number_description_test() { let mut type_option = NumberTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); + let field_type = FieldType::Number; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); assert_eq!(type_option.strip_symbol("¥18,443"), "18443".to_owned()); assert_eq!(type_option.strip_symbol("$18,443"), "18443".to_owned()); assert_eq!(type_option.strip_symbol("€18.443"), "18443".to_owned()); @@ -649,86 +648,50 @@ mod tests { type_option.format = format; match format { NumberFormat::Number => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "18443".to_owned() - ); + assert_equal(&type_option, "18443", "18443", &field_type, &field_meta); } NumberFormat::USD => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "$18,443".to_owned() - ); - assert_eq!( - type_option.decode_cell_data(data(""), &field_meta).content, - "".to_owned() - ); - assert_eq!( - type_option.decode_cell_data(data("abc"), &field_meta).content, - "".to_owned() - ); + assert_equal(&type_option, "18443", "$18,443", &field_type, &field_meta); + assert_equal(&type_option, "", "", &field_type, &field_meta); + assert_equal(&type_option, "abc", "", &field_type, &field_meta); } NumberFormat::Yen => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "¥18,443".to_owned() - ); + assert_equal(&type_option, "18443", "¥18,443", &field_type, &field_meta); } NumberFormat::Yuan => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "CN¥18,443".to_owned() - ); + assert_equal(&type_option, "18443", "CN¥18,443", &field_type, &field_meta); } NumberFormat::EUR => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "€18.443".to_owned() - ); + assert_equal(&type_option, "18443", "€18.443", &field_type, &field_meta); } _ => {} } } } - fn data(s: &str) -> String { - TypeOptionCellData::new(s, FieldType::Number).json() - } - #[test] fn number_description_scale_test() { let mut type_option = NumberTypeOption { scale: 1, ..Default::default() }; - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); + let field_type = FieldType::Number; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); for format in NumberFormat::iter() { type_option.format = format; match format { NumberFormat::Number => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "18443".to_owned() - ); + assert_equal(&type_option, "18443", "18443", &field_type, &field_meta); } NumberFormat::USD => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "$1,844.3".to_owned() - ); + assert_equal(&type_option, "18443", "$1,844.3", &field_type, &field_meta); } NumberFormat::Yen => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "¥1,844.3".to_owned() - ); + assert_equal(&type_option, "18443", "¥1,844.3", &field_type, &field_meta); } NumberFormat::EUR => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "€1.844,3".to_owned() - ); + assert_equal(&type_option, "18443", "€1.844,3", &field_type, &field_meta); } _ => {} } @@ -741,37 +704,42 @@ mod tests { sign_positive: false, ..Default::default() }; - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); + let field_type = FieldType::Number; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); for format in NumberFormat::iter() { type_option.format = format; match format { NumberFormat::Number => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "18443".to_owned() - ); + assert_equal(&type_option, "18443", "18443", &field_type, &field_meta); } NumberFormat::USD => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "-$18,443".to_owned() - ); + assert_equal(&type_option, "18443", "-$18,443", &field_type, &field_meta); } NumberFormat::Yen => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "-¥18,443".to_owned() - ); + assert_equal(&type_option, "18443", "-¥18,443", &field_type, &field_meta); } NumberFormat::EUR => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "-€18.443".to_owned() - ); + assert_equal(&type_option, "18443", "-€18.443", &field_type, &field_meta); } _ => {} } } } + + fn assert_equal( + type_option: &NumberTypeOption, + cell_data: &str, + expected_str: &str, + field_type: &FieldType, + field_meta: &FieldMeta, + ) { + assert_eq!( + type_option + .decode_cell_data(cell_data, field_type, field_meta) + .unwrap() + .to_string(), + expected_str.to_owned() + ); + } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs index 30ecfabd9d..81a7ff5c04 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs @@ -1,5 +1,5 @@ +use crate::entities::{CellIdentifier, CellIdentifierPayload}; use crate::impl_type_option; -use crate::services::entities::{CellIdentifier, CellIdentifierPayload}; use crate::services::field::type_options::util::get_cell_data; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData}; @@ -95,29 +95,38 @@ impl SelectOptionOperation for SingleSelectTypeOption { } } -impl CellDataOperation for SingleSelectTypeOption { - fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - if !type_option_cell_data.is_single_select() { - return DecodedCellData::default(); - } +impl CellDataOperation for SingleSelectTypeOption { + fn decode_cell_data( + &self, + encoded_data: T, + decoded_field_type: &FieldType, + _field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into, + { + if !decoded_field_type.is_select_option() { + return Ok(DecodedCellData::default()); + } - if let Some(option_id) = select_option_ids(type_option_cell_data.data).first() { - return match self.options.iter().find(|option| &option.id == option_id) { - None => DecodedCellData::default(), - Some(option) => DecodedCellData::from_content(option.name.clone()), - }; + let encoded_data = encoded_data.into(); + let mut cell_data = SelectOptionCellData { + options: self.options.clone(), + select_options: vec![], + }; + if let Some(option_id) = select_option_ids(encoded_data).first() { + if let Some(option) = self.options.iter().find(|option| &option.id == option_id) { + cell_data.select_options.push(option.clone()); } } - DecodedCellData::default() + DecodedCellData::try_from_bytes(cell_data) } - fn apply_changeset>( - &self, - changeset: T, - _cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { let changeset = changeset.into(); let select_option_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset)?; let new_cell_data: String; @@ -129,7 +138,7 @@ impl CellDataOperation for SingleSelectTypeOption { new_cell_data = "".to_string() } - Ok(TypeOptionCellData::new(&new_cell_data, self.field_type()).json()) + Ok(new_cell_data) } } @@ -184,31 +193,38 @@ impl SelectOptionOperation for MultiSelectTypeOption { } } -impl CellDataOperation for MultiSelectTypeOption { - fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - if !type_option_cell_data.is_multi_select() { - return DecodedCellData::default(); - } - let option_ids = select_option_ids(type_option_cell_data.data); - let content = self - .options - .iter() - .filter(|option| option_ids.contains(&option.id)) - .map(|option| option.name.clone()) - .collect::>() - .join(SELECTION_IDS_SEPARATOR); - DecodedCellData::from_content(content) - } else { - DecodedCellData::default() +impl CellDataOperation for MultiSelectTypeOption { + fn decode_cell_data( + &self, + encoded_data: T, + decoded_field_type: &FieldType, + _field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into, + { + if !decoded_field_type.is_select_option() { + return Ok(DecodedCellData::default()); } + + let encoded_data = encoded_data.into(); + let select_options = select_option_ids(encoded_data) + .into_iter() + .flat_map(|option_id| self.options.iter().find(|option| option.id == option_id).cloned()) + .collect::>(); + + let cell_data = SelectOptionCellData { + options: self.options.clone(), + select_options, + }; + + DecodedCellData::try_from_bytes(cell_data) } - fn apply_changeset>( - &self, - changeset: T, - cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: T, cell_meta: Option) -> Result + where + T: Into, + { let content_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset.into())?; let new_cell_data: String; match cell_meta { @@ -237,7 +253,7 @@ impl CellDataOperation for MultiSelectTypeOption { } } - Ok(TypeOptionCellData::new(&new_cell_data, self.field_type()).json()) + Ok(new_cell_data) } } @@ -268,7 +284,7 @@ fn select_option_ids(data: String) -> Vec { .collect::>() } -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, ProtoBuf)] pub struct SelectOption { #[pb(index = 1)] pub id: String, @@ -434,7 +450,7 @@ pub struct SelectOptionCellData { pub select_options: Vec, } -#[derive(ProtoBuf_Enum, Serialize, Deserialize, Debug, Clone)] +#[derive(ProtoBuf_Enum, PartialEq, Eq, Serialize, Deserialize, Debug, Clone)] #[repr(u8)] pub enum SelectOptionColor { Purple = 0, @@ -490,9 +506,10 @@ mod tests { use crate::services::field::FieldBuilder; use crate::services::field::{ MultiSelectTypeOption, MultiSelectTypeOptionBuilder, SelectOption, SelectOptionCellContentChangeset, - SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR, + SelectOptionCellData, SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR, }; use crate::services::row::CellDataOperation; + use flowy_grid_data_model::entities::FieldMeta; #[test] fn single_select_test() { @@ -514,29 +531,24 @@ mod tests { let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR); let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - type_option.decode_cell_data(cell_data, &field_meta).content, - google_option.name, - ); + assert_single_select_options(cell_data, &type_option, &field_meta, vec![google_option.clone()]); let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - type_option.decode_cell_data(cell_data, &field_meta).content, - google_option.name, - ); + assert_single_select_options(cell_data, &type_option, &field_meta, vec![google_option]); // Invalid option id let cell_data = type_option .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) .unwrap(); - assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); + assert_single_select_options(cell_data, &type_option, &field_meta, vec![]); // Invalid option id let cell_data = type_option .apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None) .unwrap(); - assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); + + assert_single_select_options(cell_data, &type_option, &field_meta, vec![]); // Invalid changeset assert!(type_option.apply_changeset("123", None).is_err()); @@ -562,31 +574,64 @@ mod tests { let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - type_option.decode_cell_data(cell_data, &field_meta).content, - vec![google_option.name.clone(), facebook_option.name].join(SELECTION_IDS_SEPARATOR), + assert_multi_select_options( + cell_data, + &type_option, + &field_meta, + vec![google_option.clone(), facebook_option], ); let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - type_option.decode_cell_data(cell_data, &field_meta).content, - google_option.name, - ); + assert_multi_select_options(cell_data, &type_option, &field_meta, vec![google_option]); // Invalid option id let cell_data = type_option .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) .unwrap(); - assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); + assert_multi_select_options(cell_data, &type_option, &field_meta, vec![]); // Invalid option id let cell_data = type_option .apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None) .unwrap(); - assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); + assert_multi_select_options(cell_data, &type_option, &field_meta, vec![]); // Invalid changeset assert!(type_option.apply_changeset("123", None).is_err()); } + + fn assert_multi_select_options( + cell_data: String, + type_option: &MultiSelectTypeOption, + field_meta: &FieldMeta, + expected: Vec, + ) { + assert_eq!( + expected, + type_option + .decode_cell_data(cell_data, &field_meta.field_type, field_meta) + .unwrap() + .parse::() + .unwrap() + .select_options, + ); + } + + fn assert_single_select_options( + cell_data: String, + type_option: &SingleSelectTypeOption, + field_meta: &FieldMeta, + expected: Vec, + ) { + assert_eq!( + expected, + type_option + .decode_cell_data(cell_data, &field_meta.field_type, field_meta) + .unwrap() + .parse::() + .unwrap() + .select_options, + ); + } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs index 348114e122..3acdfd97c5 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs @@ -1,16 +1,13 @@ use crate::impl_type_option; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{ - decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData, -}; +use crate::services::row::{decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData}; use bytes::Bytes; use flowy_derive::ProtoBuf; -use flowy_error::FlowyError; +use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{ CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, }; use serde::{Deserialize, Serialize}; -use std::str::FromStr; #[derive(Default)] pub struct RichTextTypeOptionBuilder(RichTextTypeOption); @@ -30,37 +27,41 @@ impl TypeOptionBuilder for RichTextTypeOptionBuilder { #[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)] pub struct RichTextTypeOption { #[pb(index = 1)] - pub format: String, + data: String, //It's not used. } impl_type_option!(RichTextTypeOption, FieldType::RichText); -impl CellDataOperation for RichTextTypeOption { - fn decode_cell_data(&self, data: String, field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - if type_option_cell_data.is_date() - || type_option_cell_data.is_single_select() - || type_option_cell_data.is_multi_select() - || type_option_cell_data.is_number() - { - decode_cell_data(data, field_meta, &type_option_cell_data.field_type).unwrap_or_default() - } else { - DecodedCellData::from_content(type_option_cell_data.data) - } +impl CellDataOperation for RichTextTypeOption { + fn decode_cell_data( + &self, + encoded_data: T, + decoded_field_type: &FieldType, + field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into, + { + if decoded_field_type.is_date() + || decoded_field_type.is_single_select() + || decoded_field_type.is_multi_select() + || decoded_field_type.is_number() + { + decode_cell_data(encoded_data, decoded_field_type, decoded_field_type, field_meta) } else { - DecodedCellData::default() + let cell_data = encoded_data.into(); + Ok(DecodedCellData::new(cell_data)) } } - fn apply_changeset>( - &self, - changeset: T, - _cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { let data = changeset.into(); if data.len() > 10000 { Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000")) } else { - Ok(TypeOptionCellData::new(&data, self.field_type()).json()) + Ok(data.0) } } } @@ -69,7 +70,7 @@ impl CellDataOperation for RichTextTypeOption { mod tests { use crate::services::field::FieldBuilder; use crate::services::field::*; - use crate::services::row::{CellDataOperation, TypeOptionCellData}; + use crate::services::row::CellDataOperation; use flowy_grid_data_model::entities::FieldType; #[test] @@ -77,25 +78,33 @@ mod tests { let type_option = RichTextTypeOption::default(); // date - let date_time_field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); + let field_type = FieldType::DateTime; + let date_time_field_meta = FieldBuilder::from_field_type(&field_type).build(); let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(1647251762, None)).unwrap(); - let data = TypeOptionCellData::new(&json, FieldType::DateTime).json(); assert_eq!( - type_option.decode_cell_data(data, &date_time_field_meta).content, + type_option + .decode_cell_data(json, &field_type, &date_time_field_meta) + .unwrap() + .parse::() + .unwrap() + .date, "Mar 14,2022".to_owned() ); // Single select let done_option = SelectOption::new("Done"); let done_option_id = done_option.id.clone(); - let single_select = SingleSelectTypeOptionBuilder::default().option(done_option); + let single_select = SingleSelectTypeOptionBuilder::default().option(done_option.clone()); let single_select_field_meta = FieldBuilder::new(single_select).build(); - let cell_data = TypeOptionCellData::new(&done_option_id, FieldType::SingleSelect).json(); + assert_eq!( type_option - .decode_cell_data(cell_data, &single_select_field_meta) - .content, - "Done".to_owned() + .decode_cell_data(done_option_id, &FieldType::SingleSelect, &single_select_field_meta) + .unwrap() + .parse::() + .unwrap() + .select_options, + vec![done_option], ); // Multiple select @@ -104,24 +113,29 @@ mod tests { let ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); let cell_data_changeset = SelectOptionCellContentChangeset::from_insert(&ids).to_str(); let multi_select = MultiSelectTypeOptionBuilder::default() - .option(google_option) - .option(facebook_option); + .option(google_option.clone()) + .option(facebook_option.clone()); let multi_select_field_meta = FieldBuilder::new(multi_select).build(); let multi_type_option = MultiSelectTypeOption::from(&multi_select_field_meta); let cell_data = multi_type_option.apply_changeset(cell_data_changeset, None).unwrap(); assert_eq!( type_option - .decode_cell_data(cell_data, &multi_select_field_meta) - .content, - "Google,Facebook".to_owned() + .decode_cell_data(cell_data, &FieldType::MultiSelect, &multi_select_field_meta) + .unwrap() + .parse::() + .unwrap() + .select_options, + vec![google_option, facebook_option] ); //Number let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); let number_field_meta = FieldBuilder::new(number).build(); - let data = TypeOptionCellData::new("18443", FieldType::Number).json(); assert_eq!( - type_option.decode_cell_data(data, &number_field_meta).content, + type_option + .decode_cell_data("18443".to_owned(), &FieldType::Number, &number_field_meta) + .unwrap() + .to_string(), "$18,443".to_owned() ); } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs new file mode 100644 index 0000000000..7299b1babd --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs @@ -0,0 +1,186 @@ +use crate::impl_type_option; +use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; +use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, EncodedCellData}; +use bytes::Bytes; +use fancy_regex::Regex; +use flowy_derive::ProtoBuf; +use flowy_error::{internal_error, FlowyError, FlowyResult}; +use flowy_grid_data_model::entities::{ + CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, +}; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +#[derive(Default)] +pub struct URLTypeOptionBuilder(URLTypeOption); +impl_into_box_type_option_builder!(URLTypeOptionBuilder); +impl_builder_from_json_str_and_from_bytes!(URLTypeOptionBuilder, URLTypeOption); + +impl TypeOptionBuilder for URLTypeOptionBuilder { + fn field_type(&self) -> FieldType { + self.0.field_type() + } + + fn entry(&self) -> &dyn TypeOptionDataEntry { + &self.0 + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] +pub struct URLTypeOption { + #[pb(index = 1)] + data: String, //It's not used. +} +impl_type_option!(URLTypeOption, FieldType::URL); + +impl CellDataOperation, String> for URLTypeOption { + fn decode_cell_data( + &self, + encoded_data: T, + decoded_field_type: &FieldType, + _field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into>, + { + if !decoded_field_type.is_url() { + return Ok(DecodedCellData::default()); + } + let cell_data = encoded_data.into().try_into_inner()?; + DecodedCellData::try_from_bytes(cell_data) + } + + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { + let changeset = changeset.into(); + let mut cell_data = URLCellData { + url: "".to_string(), + content: changeset.to_string(), + }; + + if let Ok(Some(m)) = URL_REGEX.find(&changeset) { + // Only support https scheme by now + match url::Url::parse(m.as_str()) { + Ok(url) => { + if url.scheme() == "https" { + cell_data.url = url.into(); + } else { + cell_data.url = format!("https://{}", m.as_str()); + } + } + Err(_) => { + cell_data.url = format!("https://{}", m.as_str()); + } + } + } + + cell_data.to_json() + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] +pub struct URLCellData { + #[pb(index = 1)] + pub url: String, + + #[pb(index = 2)] + pub content: String, +} + +impl URLCellData { + pub fn new(s: &str) -> Self { + Self { + url: "".to_string(), + content: s.to_string(), + } + } + + fn to_json(&self) -> FlowyResult { + serde_json::to_string(self).map_err(internal_error) + } +} + +impl FromStr for URLCellData { + type Err = FlowyError; + + fn from_str(s: &str) -> Result { + serde_json::from_str::(s).map_err(internal_error) + } +} + +lazy_static! { + static ref URL_REGEX: Regex = Regex::new( + "[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)" + ) + .unwrap(); +} + +#[cfg(test)] +mod tests { + use crate::services::field::FieldBuilder; + use crate::services::field::{URLCellData, URLTypeOption}; + use crate::services::row::{CellDataOperation, EncodedCellData}; + use flowy_grid_data_model::entities::{FieldMeta, FieldType}; + + #[test] + fn url_type_option_test_no_url() { + let type_option = URLTypeOption::default(); + let field_type = FieldType::URL; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); + assert_changeset(&type_option, "123", &field_type, &field_meta, "123", ""); + } + + #[test] + fn url_type_option_test_contains_url() { + let type_option = URLTypeOption::default(); + let field_type = FieldType::URL; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); + assert_changeset( + &type_option, + "AppFlowy website - https://www.appflowy.io", + &field_type, + &field_meta, + "AppFlowy website - https://www.appflowy.io", + "https://www.appflowy.io/", + ); + + assert_changeset( + &type_option, + "AppFlowy website appflowy.io", + &field_type, + &field_meta, + "AppFlowy website appflowy.io", + "https://appflowy.io", + ); + } + + fn assert_changeset( + type_option: &URLTypeOption, + cell_data: &str, + field_type: &FieldType, + field_meta: &FieldMeta, + expected: &str, + expected_url: &str, + ) { + let encoded_data = type_option.apply_changeset(cell_data, None).unwrap(); + let decode_cell_data = decode_cell_data(encoded_data, type_option, field_meta, field_type); + assert_eq!(expected.to_owned(), decode_cell_data.content); + assert_eq!(expected_url.to_owned(), decode_cell_data.url); + } + + fn decode_cell_data>>( + encoded_data: T, + type_option: &URLTypeOption, + field_meta: &FieldMeta, + field_type: &FieldType, + ) -> URLCellData { + type_option + .decode_cell_data(encoded_data, field_type, field_meta) + .unwrap() + .parse::() + .unwrap() + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index 44c826f31d..e4cee15a6e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -1,9 +1,9 @@ use crate::dart_notification::{send_dart_notification, GridNotification}; +use crate::entities::CellIdentifier; use crate::manager::GridUser; -use crate::services::block_meta_manager::GridBlockMetaEditorManager; -use crate::services::entities::CellIdentifier; +use crate::services::block_meta_manager::GridBlockManager; use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder}; -use crate::services::persistence::block_index::BlockIndexPersistence; +use crate::services::persistence::block_index::BlockIndexCache; use crate::services::row::*; use bytes::Bytes; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; @@ -19,20 +19,26 @@ use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; -pub struct ClientGridEditor { +pub struct GridMetaEditor { grid_id: String, user: Arc, grid_pad: Arc>, rev_manager: Arc, - block_meta_manager: Arc, + block_manager: Arc, } -impl ClientGridEditor { +impl Drop for GridMetaEditor { + fn drop(&mut self) { + tracing::trace!("Drop GridMetaEditor"); + } +} + +impl GridMetaEditor { pub async fn new( grid_id: &str, user: Arc, mut rev_manager: RevisionManager, - persistence: Arc, + persistence: Arc, ) -> FlowyResult> { let token = user.token()?; let cloud = Arc::new(GridRevisionCloudService { token }); @@ -41,13 +47,13 @@ impl ClientGridEditor { let grid_pad = Arc::new(RwLock::new(grid_pad)); let blocks = grid_pad.read().await.get_block_metas(); - let block_meta_manager = Arc::new(GridBlockMetaEditorManager::new(grid_id, &user, blocks, persistence).await?); + let block_meta_manager = Arc::new(GridBlockManager::new(grid_id, &user, blocks, persistence).await?); Ok(Arc::new(Self { grid_id: grid_id.to_owned(), user, grid_pad, rev_manager, - block_meta_manager, + block_manager: block_meta_manager, })) } @@ -254,10 +260,7 @@ impl ClientGridEditor { let row_order = RowOrder::from(&row_meta); // insert the row - let row_count = self - .block_meta_manager - .create_row(&block_id, row_meta, start_row_id) - .await?; + let row_count = self.block_manager.create_row(&block_id, row_meta, start_row_id).await?; // update block row count let changeset = GridBlockMetaChangeset::from_row_count(&block_id, row_count); @@ -277,7 +280,7 @@ impl ClientGridEditor { .or_insert_with(Vec::new) .push(row_meta); } - let changesets = self.block_meta_manager.insert_row(rows_by_block_id).await?; + let changesets = self.block_manager.insert_row(rows_by_block_id).await?; for changeset in changesets { let _ = self.update_block(changeset).await?; } @@ -286,7 +289,7 @@ impl ClientGridEditor { pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> { let field_metas = self.get_field_metas::(None).await?; - self.block_meta_manager + self.block_manager .update_row(changeset, |row_meta| make_row_from_row_meta(&field_metas, row_meta)) .await } @@ -309,7 +312,7 @@ impl ClientGridEditor { } pub async fn get_row(&self, row_id: &str) -> FlowyResult> { - match self.block_meta_manager.get_row_meta(row_id).await? { + match self.block_manager.get_row_meta(row_id).await? { None => Ok(None), Some(row_meta) => { let field_metas = self.get_field_metas::(None).await?; @@ -321,7 +324,7 @@ impl ClientGridEditor { } } pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> { - let _ = self.block_meta_manager.delete_row(row_id).await?; + let _ = self.block_manager.delete_row(row_id).await?; Ok(()) } @@ -331,12 +334,12 @@ impl ClientGridEditor { pub async fn get_cell(&self, params: &CellIdentifier) -> Option { let field_meta = self.get_field_meta(¶ms.field_id).await?; - let row_meta = self.block_meta_manager.get_row_meta(¶ms.row_id).await.ok()??; + let row_meta = self.block_manager.get_row_meta(¶ms.row_id).await.ok()??; make_cell(¶ms.field_id, &field_meta, &row_meta) } pub async fn get_cell_meta(&self, row_id: &str, field_id: &str) -> FlowyResult> { - let row_meta = self.block_meta_manager.get_row_meta(row_id).await?; + let row_meta = self.block_manager.get_row_meta(row_id).await?; match row_meta { None => Ok(None), Some(row_meta) => { @@ -382,7 +385,7 @@ impl ClientGridEditor { cell_content_changeset, }; let _ = self - .block_meta_manager + .block_manager .update_cell(cell_changeset, |row_meta| { make_row_from_row_meta(&field_metas, row_meta) }) @@ -403,7 +406,7 @@ impl ClientGridEditor { } pub async fn delete_rows(&self, row_orders: Vec) -> FlowyResult<()> { - let changesets = self.block_meta_manager.delete_rows(row_orders).await?; + let changesets = self.block_manager.delete_rows(row_orders).await?; for changeset in changesets { let _ = self.update_block(changeset).await?; } @@ -415,7 +418,7 @@ impl ClientGridEditor { let field_orders = pad_read_guard.get_field_orders(); let mut block_orders = vec![]; for block_order in pad_read_guard.get_block_metas() { - let row_orders = self.block_meta_manager.get_row_orders(&block_order.block_id).await?; + let row_orders = self.block_manager.get_row_orders(&block_order.block_id).await?; let block_order = GridBlockOrder { block_id: block_order.block_id, row_orders, @@ -442,7 +445,7 @@ impl ClientGridEditor { .collect::>(), Some(block_ids) => block_ids, }; - let snapshots = self.block_meta_manager.make_block_snapshots(block_ids).await?; + let snapshots = self.block_manager.make_block_snapshots(block_ids).await?; Ok(snapshots) } @@ -476,10 +479,7 @@ impl ClientGridEditor { } pub async fn move_row(&self, row_id: &str, from: i32, to: i32) -> FlowyResult<()> { - let _ = self - .block_meta_manager - .move_row(row_id, from as usize, to as usize) - .await?; + let _ = self.block_manager.move_row(row_id, from as usize, to as usize).await?; Ok(()) } @@ -565,7 +565,7 @@ impl ClientGridEditor { } #[cfg(feature = "flowy_unit_test")] -impl ClientGridEditor { +impl GridMetaEditor { pub fn rev_manager(&self) -> Arc { self.rev_manager.clone() } diff --git a/frontend/rust-lib/flowy-grid/src/services/mod.rs b/frontend/rust-lib/flowy-grid/src/services/mod.rs index 036efd6ada..c9a8217bd8 100644 --- a/frontend/rust-lib/flowy-grid/src/services/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/mod.rs @@ -2,7 +2,6 @@ mod util; pub mod block_meta_editor; mod block_meta_manager; -pub mod entities; pub mod field; pub mod grid_editor; pub mod persistence; diff --git a/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs b/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs index df75ec629c..c62dc502ad 100644 --- a/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs +++ b/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs @@ -7,11 +7,11 @@ use flowy_database::{ use flowy_error::FlowyResult; use std::sync::Arc; -pub struct BlockIndexPersistence { +pub struct BlockIndexCache { database: Arc, } -impl BlockIndexPersistence { +impl BlockIndexCache { pub fn new(database: Arc) -> Self { Self { database } } @@ -26,7 +26,7 @@ impl BlockIndexPersistence { Ok(block_id) } - pub fn insert_or_update(&self, block_id: &str, row_id: &str) -> FlowyResult<()> { + pub fn insert(&self, block_id: &str, row_id: &str) -> FlowyResult<()> { let conn = self.database.db_connection()?; let item = IndexItem { row_id: row_id.to_string(), diff --git a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs index 5f6dbc74fd..d93f84244a 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs @@ -1,20 +1,31 @@ use crate::services::field::*; -use flowy_error::FlowyError; +use bytes::Bytes; +use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{CellMeta, FieldMeta, FieldType}; use serde::{Deserialize, Serialize}; use std::fmt::Formatter; +use std::str::FromStr; -pub trait CellDataOperation { - fn decode_cell_data(&self, data: String, field_meta: &FieldMeta) -> DecodedCellData; - fn apply_changeset>( +pub trait CellDataOperation { + fn decode_cell_data( &self, - changeset: T, + encoded_data: T, + decoded_field_type: &FieldType, + field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into; + + // + fn apply_changeset>( + &self, + changeset: C, cell_meta: Option, - ) -> Result; + ) -> FlowyResult; } #[derive(Debug)] -pub struct CellContentChangeset(String); +pub struct CellContentChangeset(pub String); impl std::fmt::Display for CellContentChangeset { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -43,6 +54,12 @@ pub struct TypeOptionCellData { pub field_type: FieldType, } +impl TypeOptionCellData { + pub fn split(self) -> (String, FieldType) { + (self.data, self.field_type) + } +} + impl std::str::FromStr for TypeOptionCellData { type Err = FlowyError; @@ -52,6 +69,14 @@ impl std::str::FromStr for TypeOptionCellData { } } +impl std::convert::TryInto for String { + type Error = FlowyError; + + fn try_into(self) -> Result { + TypeOptionCellData::from_str(&self) + } +} + impl TypeOptionCellData { pub fn new(data: T, field_type: FieldType) -> Self { TypeOptionCellData { @@ -87,6 +112,10 @@ impl TypeOptionCellData { pub fn is_multi_select(&self) -> bool { self.field_type == FieldType::MultiSelect } + + pub fn is_select_option(&self) -> bool { + self.field_type == FieldType::MultiSelect || self.field_type == FieldType::SingleSelect + } } /// The changeset will be deserialized into specific data base on the FieldType. @@ -96,63 +125,147 @@ pub fn apply_cell_data_changeset>( cell_meta: Option, field_meta: &FieldMeta, ) -> Result { - match field_meta.field_type { + let s = match field_meta.field_type { FieldType::RichText => RichTextTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), FieldType::Number => NumberTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), - FieldType::DateTime => DateTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), + FieldType::DateTime => DateTypeOption::from(field_meta) + .apply_changeset(changeset, cell_meta) + .map(|data| data.to_string()), FieldType::SingleSelect => SingleSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), FieldType::MultiSelect => MultiSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), FieldType::Checkbox => CheckboxTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), + FieldType::URL => URLTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), + }?; + + Ok(TypeOptionCellData::new(s, field_meta.field_type.clone()).json()) +} + +pub fn decode_cell_data_from_type_option_cell_data>( + data: T, + field_meta: &FieldMeta, + field_type: &FieldType, +) -> DecodedCellData { + if let Ok(type_option_cell_data) = data.try_into() { + let (encoded_data, s_field_type) = type_option_cell_data.split(); + match decode_cell_data(encoded_data, &s_field_type, field_type, field_meta) { + Ok(cell_data) => cell_data, + Err(e) => { + tracing::error!("Decode cell data failed, {:?}", e); + DecodedCellData::default() + } + } + } else { + tracing::error!("Decode type option data failed"); + DecodedCellData::default() } } -pub fn decode_cell_data(data: String, field_meta: &FieldMeta, field_type: &FieldType) -> Option { - let s = match field_type { - FieldType::RichText => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - FieldType::Number => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - FieldType::DateTime => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - FieldType::SingleSelect => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - FieldType::MultiSelect => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - FieldType::Checkbox => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), +pub fn decode_cell_data>( + encoded_data: T, + s_field_type: &FieldType, + t_field_type: &FieldType, + field_meta: &FieldMeta, +) -> FlowyResult { + let encoded_data = encoded_data.into(); + let get_cell_data = || { + let data = match t_field_type { + FieldType::RichText => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::Number => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::DateTime => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::SingleSelect => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::MultiSelect => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::Checkbox => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::URL => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + }; + Some(data) }; - tracing::Span::current().record( - "content", - &format!("{:?}: {}", field_meta.field_type, s.content).as_str(), - ); - Some(s) + + match get_cell_data() { + Some(Ok(data)) => Ok(data), + Some(Err(err)) => { + tracing::error!("{:?}", err); + Ok(DecodedCellData::default()) + } + None => Ok(DecodedCellData::default()), + } +} + +pub(crate) struct EncodedCellData(pub Option); + +impl EncodedCellData { + pub fn try_into_inner(self) -> FlowyResult { + match self.0 { + None => Err(ErrorCode::InvalidData.into()), + Some(data) => Ok(data), + } + } +} + +impl std::convert::From for EncodedCellData +where + T: FromStr, +{ + fn from(s: String) -> Self { + match T::from_str(&s) { + Ok(inner) => EncodedCellData(Some(inner)), + Err(e) => { + tracing::error!("Deserialize Cell Data failed: {}", e); + EncodedCellData(None) + } + } + } } #[derive(Default)] pub struct DecodedCellData { - pub raw: String, - pub content: String, + pub data: Vec, } impl DecodedCellData { - pub fn from_content(content: String) -> Self { + pub fn new>(data: T) -> Self { Self { - raw: content.clone(), - content, + data: data.as_ref().to_vec(), } } - pub fn new(raw: String, content: String) -> Self { - Self { raw, content } + pub fn try_from_bytes>(bytes: T) -> FlowyResult + where + >::Error: std::fmt::Debug, + { + let bytes = bytes.try_into().map_err(internal_error)?; + Ok(Self { data: bytes.to_vec() }) } - pub fn split(self) -> (String, String) { - (self.raw, self.content) + pub fn parse<'a, T: TryFrom<&'a [u8]>>(&'a self) -> FlowyResult + where + >::Error: std::fmt::Debug, + { + T::try_from(self.data.as_ref()).map_err(internal_error) + } +} + +impl ToString for DecodedCellData { + fn to_string(&self) -> String { + match String::from_utf8(self.data.clone()) { + Ok(s) => s, + Err(e) => { + tracing::error!("DecodedCellData to string failed: {:?}", e); + "".to_string() + } + } } } diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs index edcb102595..865058ffb6 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs @@ -1,4 +1,4 @@ -use crate::services::row::decode_cell_data; +use crate::services::row::decode_cell_data_from_type_option_cell_data; use flowy_error::FlowyResult; use flowy_grid_data_model::entities::{ Cell, CellMeta, FieldMeta, GridBlock, GridBlockOrder, RepeatedGridBlock, Row, RowMeta, RowOrder, @@ -31,15 +31,15 @@ pub fn make_cell_by_field_id( cell_meta: CellMeta, ) -> Option<(String, Cell)> { let field_meta = field_map.get(&field_id)?; - let (raw, content) = decode_cell_data(cell_meta.data, field_meta, &field_meta.field_type)?.split(); - let cell = Cell::new(&field_id, content, raw); + let data = decode_cell_data_from_type_option_cell_data(cell_meta.data, field_meta, &field_meta.field_type).data; + let cell = Cell::new(&field_id, data); Some((field_id, cell)) } pub fn make_cell(field_id: &str, field_meta: &FieldMeta, row_meta: &RowMeta) -> Option { let cell_meta = row_meta.cells.get(field_id)?.clone(); - let (raw, content) = decode_cell_data(cell_meta.data, field_meta, &field_meta.field_type)?.split(); - Some(Cell::new(field_id, content, raw)) + let data = decode_cell_data_from_type_option_cell_data(cell_meta.data, field_meta, &field_meta.field_type).data; + Some(Cell::new(field_id, data)) } pub(crate) fn make_row_orders_from_row_metas(row_metas: &[Arc]) -> Vec { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs index a954694a16..7b210d4f95 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs @@ -2,10 +2,10 @@ use crate::grid::script::EditorScript::*; use crate::grid::script::*; use chrono::NaiveDateTime; use flowy_grid::services::field::{ - DateCellContentChangeset, MultiSelectTypeOption, SelectOption, SelectOptionCellContentChangeset, + DateCellContentChangeset, DateCellData, MultiSelectTypeOption, SelectOption, SelectOptionCellContentChangeset, SingleSelectTypeOption, SELECTION_IDS_SEPARATOR, }; -use flowy_grid::services::row::{decode_cell_data, CreateRowMetaBuilder}; +use flowy_grid::services::row::{decode_cell_data_from_type_option_cell_data, CreateRowMetaBuilder}; use flowy_grid_data_model::entities::{ CellChangeset, FieldChangesetParams, FieldType, GridBlockMeta, GridBlockMetaChangeset, RowMetaChangeset, TypeOptionDataEntry, @@ -262,6 +262,9 @@ async fn grid_row_add_cells_test() { FieldType::Checkbox => { builder.add_cell(&field.id, "false".to_string()).unwrap(); } + FieldType::URL => { + builder.add_cell(&field.id, "1".to_string()).unwrap(); + } } } let context = builder.build(); @@ -291,10 +294,10 @@ async fn grid_row_add_date_cell_test() { let date_field = date_field.unwrap(); let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone(); assert_eq!( - decode_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type) + decode_cell_data_from_type_option_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type) + .parse::() .unwrap() - .split() - .1, + .date, "2022/03/16", ); let scripts = vec![CreateRow { context }]; @@ -328,6 +331,7 @@ async fn grid_cell_update() { SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str() } FieldType::Checkbox => "1".to_string(), + FieldType::URL => "1".to_string(), }; scripts.push(UpdateCell { @@ -349,6 +353,7 @@ async fn grid_cell_update() { FieldType::SingleSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false), FieldType::MultiSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false), FieldType::Checkbox => ("2".to_string(), false), + FieldType::URL => ("2".to_string(), false), }; scripts.push(UpdateCell { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/script.rs index 611ee937e2..37c3b736d4 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/script.rs @@ -1,6 +1,6 @@ use bytes::Bytes; use flowy_grid::services::field::*; -use flowy_grid::services::grid_editor::{ClientGridEditor, GridPadBuilder}; +use flowy_grid::services::grid_editor::{GridMetaEditor, GridPadBuilder}; use flowy_grid::services::row::CreateRowMetaPayload; use flowy_grid_data_model::entities::{ BuildGridContext, CellChangeset, Field, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, GridBlockMeta, @@ -72,7 +72,7 @@ pub enum EditorScript { pub struct GridEditorTest { pub sdk: FlowySDKTest, pub grid_id: String, - pub editor: Arc, + pub editor: Arc, pub field_metas: Vec, pub grid_blocks: Vec, pub row_metas: Vec>, @@ -239,7 +239,7 @@ impl GridEditorTest { } } -async fn get_row_metas(editor: &Arc) -> Vec> { +async fn get_row_metas(editor: &Arc) -> Vec> { editor .grid_block_snapshots(None) .await @@ -354,6 +354,10 @@ fn make_template_1_grid() -> BuildGridContext { let checkbox = CheckboxTypeOptionBuilder::default(); let checkbox_field = FieldBuilder::new(checkbox).name("is done").visibility(true).build(); + // URL + let url = URLTypeOptionBuilder::default(); + let url_field = FieldBuilder::new(url).name("link").visibility(true).build(); + GridBuilder::default() .add_field(text_field) .add_field(single_select_field) @@ -361,6 +365,7 @@ fn make_template_1_grid() -> BuildGridContext { .add_field(number_field) .add_field(date_field) .add_field(checkbox_field) + .add_field(url_field) .add_empty_row() .add_empty_row() .add_empty_row() diff --git a/frontend/rust-lib/flowy-text-block/src/editor.rs b/frontend/rust-lib/flowy-text-block/src/editor.rs index d43fc24f1c..c1e50536a5 100644 --- a/frontend/rust-lib/flowy-text-block/src/editor.rs +++ b/frontend/rust-lib/flowy-text-block/src/editor.rs @@ -21,7 +21,7 @@ use lib_ws::WSConnectState; use std::sync::Arc; use tokio::sync::{mpsc, oneshot}; -pub struct ClientTextBlockEditor { +pub struct TextBlockEditor { pub doc_id: String, #[allow(dead_code)] rev_manager: Arc, @@ -30,7 +30,7 @@ pub struct ClientTextBlockEditor { edit_cmd_tx: EditorCommandSender, } -impl ClientTextBlockEditor { +impl TextBlockEditor { #[allow(unused_variables)] pub(crate) async fn new( doc_id: &str, @@ -185,7 +185,7 @@ impl ClientTextBlockEditor { pub(crate) fn receive_ws_state(&self, _state: &WSConnectState) {} } -impl std::ops::Drop for ClientTextBlockEditor { +impl std::ops::Drop for TextBlockEditor { fn drop(&mut self) { tracing::trace!("{} ClientBlockEditor was dropped", self.doc_id) } @@ -204,7 +204,7 @@ fn spawn_edit_queue( } #[cfg(feature = "flowy_unit_test")] -impl ClientTextBlockEditor { +impl TextBlockEditor { pub async fn text_block_delta(&self) -> FlowyResult { let (ret, rx) = oneshot::channel::>(); let msg = EditorCommand::ReadDelta { ret }; diff --git a/frontend/rust-lib/flowy-text-block/src/manager.rs b/frontend/rust-lib/flowy-text-block/src/manager.rs index 8a34c6916d..9325fe6001 100644 --- a/frontend/rust-lib/flowy-text-block/src/manager.rs +++ b/frontend/rust-lib/flowy-text-block/src/manager.rs @@ -1,4 +1,4 @@ -use crate::{editor::ClientTextBlockEditor, errors::FlowyError, BlockCloudService}; +use crate::{editor::TextBlockEditor, errors::FlowyError, BlockCloudService}; use bytes::Bytes; use dashmap::DashMap; use flowy_database::ConnectionPool; @@ -47,8 +47,8 @@ impl TextBlockManager { Ok(()) } - #[tracing::instrument(level = "debug", skip(self, block_id), fields(block_id), err)] - pub async fn open_block>(&self, block_id: T) -> Result, FlowyError> { + #[tracing::instrument(level = "trace", skip(self, block_id), fields(block_id), err)] + pub async fn open_block>(&self, block_id: T) -> Result, FlowyError> { let block_id = block_id.as_ref(); tracing::Span::current().record("block_id", &block_id); self.get_block_editor(block_id).await @@ -108,7 +108,7 @@ impl TextBlockManager { } impl TextBlockManager { - async fn get_block_editor(&self, block_id: &str) -> FlowyResult> { + async fn get_block_editor(&self, block_id: &str) -> FlowyResult> { match self.editor_map.get(block_id) { None => { let db_pool = self.user.db_pool()?; @@ -123,7 +123,7 @@ impl TextBlockManager { &self, block_id: &str, pool: Arc, - ) -> Result, FlowyError> { + ) -> Result, FlowyError> { let user = self.user.clone(); let token = self.user.token()?; let rev_manager = self.make_rev_manager(block_id, pool.clone())?; @@ -132,7 +132,7 @@ impl TextBlockManager { server: self.cloud_service.clone(), }); let doc_editor = - ClientTextBlockEditor::new(block_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service).await?; + TextBlockEditor::new(block_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service).await?; self.editor_map.insert(block_id, &doc_editor); Ok(doc_editor) } @@ -180,7 +180,7 @@ impl RevisionCloudService for TextBlockRevisionCloudService { } pub struct TextBlockEditorMap { - inner: DashMap>, + inner: DashMap>, } impl TextBlockEditorMap { @@ -188,14 +188,14 @@ impl TextBlockEditorMap { Self { inner: DashMap::new() } } - pub(crate) fn insert(&self, block_id: &str, doc: &Arc) { + pub(crate) fn insert(&self, block_id: &str, doc: &Arc) { if self.inner.contains_key(block_id) { log::warn!("Doc:{} already exists in cache", block_id); } self.inner.insert(block_id.to_string(), doc.clone()); } - pub(crate) fn get(&self, block_id: &str) -> Option> { + pub(crate) fn get(&self, block_id: &str) -> Option> { Some(self.inner.get(block_id)?.clone()) } diff --git a/frontend/rust-lib/flowy-text-block/tests/document/script.rs b/frontend/rust-lib/flowy-text-block/tests/document/script.rs index 5511896fc2..1722724c3f 100644 --- a/frontend/rust-lib/flowy-text-block/tests/document/script.rs +++ b/frontend/rust-lib/flowy-text-block/tests/document/script.rs @@ -1,6 +1,6 @@ use flowy_revision::disk::RevisionState; use flowy_test::{helper::ViewTest, FlowySDKTest}; -use flowy_text_block::editor::ClientTextBlockEditor; +use flowy_text_block::editor::TextBlockEditor; use flowy_text_block::TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS; use lib_ot::{core::Interval, rich_text::RichTextDelta}; use std::sync::Arc; @@ -19,7 +19,7 @@ pub enum EditorScript { pub struct TextBlockEditorTest { pub sdk: FlowySDKTest, - pub editor: Arc, + pub editor: Arc, } impl TextBlockEditorTest { diff --git a/frontend/scripts/makefile/tests.toml b/frontend/scripts/makefile/tests.toml index 89e3ea9140..b8082f651b 100644 --- a/frontend/scripts/makefile/tests.toml +++ b/frontend/scripts/makefile/tests.toml @@ -1,30 +1,18 @@ -[tasks.test_local] -category = "Build" -dependencies = ["rm_cache"] -description = "Build desktop targets." +[tasks.rust_unit_test] +run_task = { name = ["rust_lib_unit_test", "shared_lib_unit_test"] } + +[tasks.rust_lib_unit_test] +description = "Run rust-lib unit tests" script = ''' cd rust-lib -cargo test +RUST_LOG=info cargo test --no-default-features --features="sync" ''' +[tasks.shared_lib_unit_test] +description = "Run shared-lib unit test" +script = ''' +cd ../shared-lib +RUST_LOG=info cargo test --no-default-features +''' -[tasks.test_remote] -dependencies = ["rm_cache"] -script = """ -cd rust-lib -cargo test --features "flowy-folder/http_server","flowy-user/http_server" -""" - - -[tasks.run_server] -script = """ -cd backend -cargo run -""" - - -[tasks.rm_cache] -script = """ -rm -rf rust-lib/flowy-test/temp -""" \ No newline at end of file diff --git a/frontend/scripts/makefile/tool.toml b/frontend/scripts/makefile/tool.toml new file mode 100644 index 0000000000..bd0504f6fc --- /dev/null +++ b/frontend/scripts/makefile/tool.toml @@ -0,0 +1,27 @@ +[tasks.rust_clean] +script = [ + """ + cd rust-lib + cargo clean + + cd ../../shared-lib + cargo clean + + rm -rf lib-infra/.cache + """, +] +script_runner = "@shell" + +[tasks.rust_clean.windows] +script = [ + """ + cd rust-lib + cargo clean + + cd ../../shared-lib + cargo clean + + rmdir /s/q "lib-infra/.cache" + """, +] +script_runner = "@duckscript" \ No newline at end of file diff --git a/shared-lib/flowy-derive/src/proto_buf/deserialize.rs b/shared-lib/flowy-derive/src/proto_buf/deserialize.rs index e09f7ea847..f0325db3b0 100644 --- a/shared-lib/flowy-derive/src/proto_buf/deserialize.rs +++ b/shared-lib/flowy-derive/src/proto_buf/deserialize.rs @@ -30,6 +30,14 @@ pub fn make_de_token_steam(ctxt: &Ctxt, ast: &ASTContainer) -> Option for #struct_ident { + type Error = ::protobuf::ProtobufError; + fn try_from(bytes: &[u8]) -> Result { + let pb: crate::protobuf::#pb_ty = ::protobuf::Message::parse_from_bytes(bytes)?; + #struct_ident::try_from(pb) + } + } + impl std::convert::TryFrom for #struct_ident { type Error = ::protobuf::ProtobufError; fn try_from(mut pb: crate::protobuf::#pb_ty) -> Result { diff --git a/shared-lib/flowy-grid-data-model/src/entities/grid.rs b/shared-lib/flowy-grid-data-model/src/entities/grid.rs index 5cde193623..5889e1093b 100644 --- a/shared-lib/flowy-grid-data-model/src/entities/grid.rs +++ b/shared-lib/flowy-grid-data-model/src/entities/grid.rs @@ -484,17 +484,13 @@ pub struct Cell { pub field_id: String, #[pb(index = 2)] - pub content: String, - - #[pb(index = 3)] - pub data: String, + pub data: Vec, } impl Cell { - pub fn new(field_id: &str, content: String, data: String) -> Self { + pub fn new(field_id: &str, data: Vec) -> Self { Self { field_id: field_id.to_owned(), - content, data, } } @@ -502,8 +498,7 @@ impl Cell { pub fn empty(field_id: &str) -> Self { Self { field_id: field_id.to_owned(), - content: "".to_string(), - data: "".to_string(), + data: vec![], } } } @@ -880,6 +875,7 @@ pub enum FieldType { SingleSelect = 3, MultiSelect = 4, Checkbox = 5, + URL = 6, } impl std::default::Default for FieldType { @@ -912,6 +908,38 @@ impl FieldType { _ => 150, } } + + pub fn is_number(&self) -> bool { + self == &FieldType::Number + } + + pub fn is_text(&self) -> bool { + self == &FieldType::RichText + } + + pub fn is_checkbox(&self) -> bool { + self == &FieldType::Checkbox + } + + pub fn is_date(&self) -> bool { + self == &FieldType::DateTime + } + + pub fn is_single_select(&self) -> bool { + self == &FieldType::SingleSelect + } + + pub fn is_multi_select(&self) -> bool { + self == &FieldType::MultiSelect + } + + pub fn is_url(&self) -> bool { + self == &FieldType::URL + } + + pub fn is_select_option(&self) -> bool { + self == &FieldType::MultiSelect || self == &FieldType::SingleSelect + } } #[derive(Debug, Clone, Default, ProtoBuf)] diff --git a/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs index 0cda103da8..c087cc5ea3 100644 --- a/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs +++ b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs @@ -4743,8 +4743,7 @@ impl ::protobuf::reflect::ProtobufValue for GridBlock { pub struct Cell { // message fields pub field_id: ::std::string::String, - pub content: ::std::string::String, - pub data: ::std::string::String, + pub data: ::std::vec::Vec, // special fields pub unknown_fields: ::protobuf::UnknownFields, pub cached_size: ::protobuf::CachedSize, @@ -4787,36 +4786,10 @@ impl Cell { ::std::mem::replace(&mut self.field_id, ::std::string::String::new()) } - // string content = 2; + // bytes data = 2; - pub fn get_content(&self) -> &str { - &self.content - } - pub fn clear_content(&mut self) { - self.content.clear(); - } - - // Param is passed by value, moved - pub fn set_content(&mut self, v: ::std::string::String) { - self.content = v; - } - - // Mutable pointer to the field. - // If field is not initialized, it is initialized with default value first. - pub fn mut_content(&mut self) -> &mut ::std::string::String { - &mut self.content - } - - // Take field - pub fn take_content(&mut self) -> ::std::string::String { - ::std::mem::replace(&mut self.content, ::std::string::String::new()) - } - - // string data = 3; - - - pub fn get_data(&self) -> &str { + pub fn get_data(&self) -> &[u8] { &self.data } pub fn clear_data(&mut self) { @@ -4824,19 +4797,19 @@ impl Cell { } // Param is passed by value, moved - pub fn set_data(&mut self, v: ::std::string::String) { + pub fn set_data(&mut self, v: ::std::vec::Vec) { self.data = v; } // Mutable pointer to the field. // If field is not initialized, it is initialized with default value first. - pub fn mut_data(&mut self) -> &mut ::std::string::String { + pub fn mut_data(&mut self) -> &mut ::std::vec::Vec { &mut self.data } // Take field - pub fn take_data(&mut self) -> ::std::string::String { - ::std::mem::replace(&mut self.data, ::std::string::String::new()) + pub fn take_data(&mut self) -> ::std::vec::Vec { + ::std::mem::replace(&mut self.data, ::std::vec::Vec::new()) } } @@ -4853,10 +4826,7 @@ impl ::protobuf::Message for Cell { ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?; }, 2 => { - ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.content)?; - }, - 3 => { - ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?; + ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.data)?; }, _ => { ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; @@ -4873,11 +4843,8 @@ impl ::protobuf::Message for Cell { if !self.field_id.is_empty() { my_size += ::protobuf::rt::string_size(1, &self.field_id); } - if !self.content.is_empty() { - my_size += ::protobuf::rt::string_size(2, &self.content); - } if !self.data.is_empty() { - my_size += ::protobuf::rt::string_size(3, &self.data); + my_size += ::protobuf::rt::bytes_size(2, &self.data); } my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); self.cached_size.set(my_size); @@ -4888,11 +4855,8 @@ impl ::protobuf::Message for Cell { if !self.field_id.is_empty() { os.write_string(1, &self.field_id)?; } - if !self.content.is_empty() { - os.write_string(2, &self.content)?; - } if !self.data.is_empty() { - os.write_string(3, &self.data)?; + os.write_bytes(2, &self.data)?; } os.write_unknown_fields(self.get_unknown_fields())?; ::std::result::Result::Ok(()) @@ -4937,12 +4901,7 @@ impl ::protobuf::Message for Cell { |m: &Cell| { &m.field_id }, |m: &mut Cell| { &mut m.field_id }, )); - fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( - "content", - |m: &Cell| { &m.content }, - |m: &mut Cell| { &mut m.content }, - )); - fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>( "data", |m: &Cell| { &m.data }, |m: &mut Cell| { &mut m.data }, @@ -4964,7 +4923,6 @@ impl ::protobuf::Message for Cell { impl ::protobuf::Clear for Cell { fn clear(&mut self) { self.field_id.clear(); - self.content.clear(); self.data.clear(); self.unknown_fields.clear(); } @@ -8231,6 +8189,7 @@ pub enum FieldType { SingleSelect = 3, MultiSelect = 4, Checkbox = 5, + URL = 6, } impl ::protobuf::ProtobufEnum for FieldType { @@ -8246,6 +8205,7 @@ impl ::protobuf::ProtobufEnum for FieldType { 3 => ::std::option::Option::Some(FieldType::SingleSelect), 4 => ::std::option::Option::Some(FieldType::MultiSelect), 5 => ::std::option::Option::Some(FieldType::Checkbox), + 6 => ::std::option::Option::Some(FieldType::URL), _ => ::std::option::Option::None } } @@ -8258,6 +8218,7 @@ impl ::protobuf::ProtobufEnum for FieldType { FieldType::SingleSelect, FieldType::MultiSelect, FieldType::Checkbox, + FieldType::URL, ]; values } @@ -8340,50 +8301,49 @@ static file_descriptor_proto_data: &'static [u8] = b"\ derR\x0bdeletedRows\x123\n\x0cupdated_rows\x18\x04\x20\x03(\x0b2\x10.Upd\ atedRowOrderR\x0bupdatedRows\"E\n\tGridBlock\x12\x0e\n\x02id\x18\x01\x20\ \x01(\tR\x02id\x12(\n\nrow_orders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trow\ - Orders\"O\n\x04Cell\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\ - \x12\x18\n\x07content\x18\x02\x20\x01(\tR\x07content\x12\x12\n\x04data\ - \x18\x03\x20\x01(\tR\x04data\"+\n\x0cRepeatedCell\x12\x1b\n\x05items\x18\ - \x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\x11CreateGridPayload\x12\x12\ - \n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\n\x05va\ - lue\x18\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlockId\x12\x14\n\x05value\ - \x18\x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayload\x12\x17\n\x07grid\ - _id\x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_row_id\x18\x02\x20\ - \x01(\tH\0R\nstartRowIdB\x15\n\x13one_of_start_row_id\"\xb6\x01\n\x12Ins\ - ertFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\ - \x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.FieldR\x05field\x12(\n\x10type\ - _option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\x12&\n\x0estart_fie\ - ld_id\x18\x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\x15one_of_start_fiel\ - d_id\"|\n\x1cUpdateFieldTypeOptionPayload\x12\x17\n\x07grid_id\x18\x01\ - \x20\x01(\tR\x06gridId\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\x07fiel\ - dId\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\"\ - d\n\x11QueryFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gri\ - dId\x126\n\x0cfield_orders\x18\x02\x20\x01(\x0b2\x13.RepeatedFieldOrderR\ - \x0bfieldOrders\"e\n\x16QueryGridBlocksPayload\x12\x17\n\x07grid_id\x18\ - \x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orders\x18\x02\x20\x03(\x0b2\ - \x0f.GridBlockOrderR\x0bblockOrders\"\xa8\x03\n\x15FieldChangesetPayload\ - \x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x17\n\x07grid_\ - id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\n\x04name\x18\x03\x20\x01(\tH\0\ - R\x04name\x12\x14\n\x04desc\x18\x04\x20\x01(\tH\x01R\x04desc\x12+\n\nfie\ - ld_type\x18\x05\x20\x01(\x0e2\n.FieldTypeH\x02R\tfieldType\x12\x18\n\x06\ - frozen\x18\x06\x20\x01(\x08H\x03R\x06frozen\x12\x20\n\nvisibility\x18\ - \x07\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05width\x18\x08\x20\x01(\ - \x05H\x05R\x05width\x12*\n\x10type_option_data\x18\t\x20\x01(\x0cH\x06R\ - \x0etypeOptionDataB\r\n\x0bone_of_nameB\r\n\x0bone_of_descB\x13\n\x11one\ - _of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_of_visibilityB\x0e\n\ - \x0cone_of_widthB\x19\n\x17one_of_type_option_data\"\x9c\x01\n\x0fMoveIt\ - emPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x17\n\ - \x07item_id\x18\x02\x20\x01(\tR\x06itemId\x12\x1d\n\nfrom_index\x18\x03\ - \x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_index\x18\x04\x20\x01(\x05R\ - \x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\x0e2\r.MoveItemTypeR\x02ty\ - \"\xb3\x01\n\rCellChangeset\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06\ - gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\x08fie\ - ld_id\x18\x03\x20\x01(\tR\x07fieldId\x126\n\x16cell_content_changeset\ - \x18\x04\x20\x01(\tH\0R\x14cellContentChangesetB\x1f\n\x1done_of_cell_co\ - ntent_changeset**\n\x0cMoveItemType\x12\r\n\tMoveField\x10\0\x12\x0b\n\ - \x07MoveRow\x10\x01*d\n\tFieldType\x12\x0c\n\x08RichText\x10\0\x12\n\n\ - \x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\x0cSingleSele\ - ct\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08Checkbox\x10\ - \x05b\x06proto3\ + Orders\"5\n\x04Cell\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\ + \x12\x12\n\x04data\x18\x02\x20\x01(\x0cR\x04data\"+\n\x0cRepeatedCell\ + \x12\x1b\n\x05items\x18\x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\x11Cre\ + ateGridPayload\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06\ + GridId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlock\ + Id\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayl\ + oad\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_\ + row_id\x18\x02\x20\x01(\tH\0R\nstartRowIdB\x15\n\x13one_of_start_row_id\ + \"\xb6\x01\n\x12InsertFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\ + \tR\x06gridId\x12\x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.FieldR\x05fie\ + ld\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\ + \x12&\n\x0estart_field_id\x18\x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\ + \x15one_of_start_field_id\"|\n\x1cUpdateFieldTypeOptionPayload\x12\x17\n\ + \x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_id\x18\x02\ + \x20\x01(\tR\x07fieldId\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\ + \x0etypeOptionData\"d\n\x11QueryFieldPayload\x12\x17\n\x07grid_id\x18\ + \x01\x20\x01(\tR\x06gridId\x126\n\x0cfield_orders\x18\x02\x20\x01(\x0b2\ + \x13.RepeatedFieldOrderR\x0bfieldOrders\"e\n\x16QueryGridBlocksPayload\ + \x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orde\ + rs\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\x0bblockOrders\"\xa8\x03\n\ + \x15FieldChangesetPayload\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07f\ + ieldId\x12\x17\n\x07grid_id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\n\x04n\ + ame\x18\x03\x20\x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\x04\x20\x01(\tH\ + \x01R\x04desc\x12+\n\nfield_type\x18\x05\x20\x01(\x0e2\n.FieldTypeH\x02R\ + \tfieldType\x12\x18\n\x06frozen\x18\x06\x20\x01(\x08H\x03R\x06frozen\x12\ + \x20\n\nvisibility\x18\x07\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05w\ + idth\x18\x08\x20\x01(\x05H\x05R\x05width\x12*\n\x10type_option_data\x18\ + \t\x20\x01(\x0cH\x06R\x0etypeOptionDataB\r\n\x0bone_of_nameB\r\n\x0bone_\ + of_descB\x13\n\x11one_of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_\ + of_visibilityB\x0e\n\x0cone_of_widthB\x19\n\x17one_of_type_option_data\"\ + \x9c\x01\n\x0fMoveItemPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\ + \x06gridId\x12\x17\n\x07item_id\x18\x02\x20\x01(\tR\x06itemId\x12\x1d\n\ + \nfrom_index\x18\x03\x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_index\x18\ + \x04\x20\x01(\x05R\x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\x0e2\r.Mo\ + veItemTypeR\x02ty\"\xb3\x01\n\rCellChangeset\x12\x17\n\x07grid_id\x18\ + \x01\x20\x01(\tR\x06gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05ro\ + wId\x12\x19\n\x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x126\n\x16cell_\ + content_changeset\x18\x04\x20\x01(\tH\0R\x14cellContentChangesetB\x1f\n\ + \x1done_of_cell_content_changeset**\n\x0cMoveItemType\x12\r\n\tMoveField\ + \x10\0\x12\x0b\n\x07MoveRow\x10\x01*m\n\tFieldType\x12\x0c\n\x08RichText\ + \x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\ + \x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08C\ + heckbox\x10\x05\x12\x07\n\x03URL\x10\x06b\x06proto3\ "; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; diff --git a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto index a2d74e96a3..f06d84e5b8 100644 --- a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto +++ b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto @@ -95,8 +95,7 @@ message GridBlock { } message Cell { string field_id = 1; - string content = 2; - string data = 3; + bytes data = 2; } message RepeatedCell { repeated Cell items = 1; @@ -168,4 +167,5 @@ enum FieldType { SingleSelect = 3; MultiSelect = 4; Checkbox = 5; + URL = 6; } diff --git a/shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs index 3f727faf59..3370719e68 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs @@ -175,7 +175,7 @@ impl GridBlockMetaPad { match cal_diff::(old, new) { None => Ok(None), Some(delta) => { - tracing::debug!("[GridBlockMeta] Composing delta {}", delta.to_delta_str()); + tracing::trace!("[GridBlockMeta] Composing delta {}", delta.to_delta_str()); // tracing::debug!( // "[GridBlockMeta] current delta: {}", // self.delta.to_str().unwrap_or_else(|_| "".to_string()) diff --git a/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs b/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs index a65d4d5bd9..a44f0cb267 100644 --- a/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs +++ b/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs @@ -153,27 +153,22 @@ pub fn check_pb_dart_plugin() { let output = Command::new("sh").arg("-c").arg("echo $PATH").output(); let paths = String::from_utf8(output.unwrap().stdout) .unwrap() - .split(":") + .split(':') .map(|s| s.to_string()) .collect::>(); paths.iter().for_each(|s| msg.push_str(&format!("{}\n", s))); - match Command::new("sh").arg("-c").arg("which protoc-gen-dart").output() { - Ok(output) => { - msg.push_str(&format!( - "Installed protoc-gen-dart path: {:?}\n", - String::from_utf8(output.stdout).unwrap() - )); - } - Err(_) => {} + if let Ok(output) = Command::new("sh").arg("-c").arg("which protoc-gen-dart").output() { + msg.push_str(&format!( + "Installed protoc-gen-dart path: {:?}\n", + String::from_utf8(output.stdout).unwrap() + )); } - msg.push_str(&format!("✅ You can fix that by adding:")); - msg.push_str(&format!("\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n",)); - msg.push_str(&format!( - "to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)" - )); + msg.push_str(&"✅ You can fix that by adding:".to_string()); + msg.push_str(&"\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n".to_string()); + msg.push_str(&"to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)".to_string()); panic!("{}", msg) } }