diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ab1becf24f..d3dd050e2c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,7 +18,7 @@ jobs: - os: ubuntu-latest flutter_profile: development-linux-x86 - os: macos-latest - flutter_profile: development-mac + flutter_profile: development-mac-x86_64 runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4d500c4659..a2c9c7b133 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -113,7 +113,7 @@ jobs: working-directory: frontend run: | flutter config --enable-macos-desktop - cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-mac-x86 appflowy + cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-mac-x86_64 appflowy - name: Archive macOS app working-directory: ${{ env.MACOS_APP_RELEASE_PATH }} diff --git a/CHANGELOG.md b/CHANGELOG.md index a5ac013c35..5ee49d36f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # Release Notes +## Version 0.0.4 - 2022-06-06 +- Drag to adjust the width of a column +- Upgrade to Flutter 3.0 +- Native support for M1 chip +- Date supports time formats +- New property: URL +- Keyboard shortcuts support for Grid: press Enter to leave the edit mode; control c/v to copy-paste cell values + +### Bug Fixes +- Fixed some bugs + + +## Version 0.0.4 - beta.3 - 2022-05-02 +- Drag to reorder app/ view/ field +- Row record open as a page +- Auto resize the height of the row in the grid +- Support more number formats +- Search column options, supporting Single select, Multi-select, and number format + +![May-03-2022 10-03-00](https://user-images.githubusercontent.com/86001920/166394640-a8f1f3bc-5f20-4033-93e9-16bc308d7005.gif) + + +### Bug Fixes & Improvements +- Improved row/cell data cache +- Fixed some bugs + + +## Version 0.0.4 - beta.2 - 2022-04-11 + + - Support properties: Text, Number, Date, Checkbox, Select, Multi-select + - Insert / delete rows + - Add / delete / hide columns + - Edit property + ![](https://user-images.githubusercontent.com/12026239/162753644-bf2f4e7a-2367-4d48-87e6-35e244e83a5b.png) + ## Version 0.0.4 - beta.1 - 2022-04-08 v0.0.4 - beta.1 is pre-release diff --git a/frontend/Makefile.toml b/frontend/Makefile.toml index c46687a2df..42708d7c32 100644 --- a/frontend/Makefile.toml +++ b/frontend/Makefile.toml @@ -45,7 +45,15 @@ APP_ENVIRONMENT = "local" FLUTTER_FLOWY_SDK_PATH="app_flowy/packages/flowy_sdk" PROTOBUF_DERIVE_CACHE="../shared-lib/flowy-derive/src/derive_cache/derive_cache.rs" -[env.development-mac] +[env.development-mac-arm64] +RUST_LOG = "info" +TARGET_OS = "macos" +RUST_COMPILE_TARGET = "aarch64-apple-darwin" +BUILD_FLAG = "debug" +FLUTTER_OUTPUT_DIR = "Debug" +PRODUCT_EXT = "app" + +[env.development-mac-x86_64] RUST_LOG = "info" TARGET_OS = "macos" RUST_COMPILE_TARGET = "x86_64-apple-darwin" @@ -53,21 +61,23 @@ BUILD_FLAG = "debug" FLUTTER_OUTPUT_DIR = "Debug" PRODUCT_EXT = "app" -[env.production-mac-aarch64] +[env.production-mac-arm64] BUILD_FLAG = "release" TARGET_OS = "macos" RUST_COMPILE_TARGET = "aarch64-apple-darwin" FLUTTER_OUTPUT_DIR = "Release" PRODUCT_EXT = "app" APP_ENVIRONMENT = "production" +BUILD_ARCHS = "arm64" -[env.production-mac-x86] +[env.production-mac-x86_64] BUILD_FLAG = "release" TARGET_OS = "macos" RUST_COMPILE_TARGET = "x86_64-apple-darwin" FLUTTER_OUTPUT_DIR = "Release" PRODUCT_EXT = "app" APP_ENVIRONMENT = "production" +BUILD_ARCHS = "x86_64" [env.development-windows-x86] TARGET_OS = "windows" @@ -138,6 +148,7 @@ script = [ echo PRODUCT_EXT: ${PRODUCT_EXT} echo APP_ENVIRONMENT: ${APP_ENVIRONMENT} echo ${platforms} + echo ${BUILD_ARCHS} ''' ] script_runner = "@shell" diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index ce1f984f52..6a919ed8ba 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -186,7 +186,8 @@ "row": { "duplicate": "Duplicate", "delete": "Delete", - "textPlaceholder": "Empty" + "textPlaceholder": "Empty", + "copyProperty": "Copied property to clipboard" }, "selectOption": { "create": "Create", @@ -203,6 +204,10 @@ "colorPannelTitle": "Colors", "pannelTitle": "Select an option or create one", "searchOption": "Search for an option" + }, + "date": { + "timeHintTextInTwelveHour": "12:00 AM", + "timeHintTextInTwentyFourHour": "12:00" } } } diff --git a/frontend/app_flowy/assets/translations/pt-BR.json b/frontend/app_flowy/assets/translations/pt-BR.json index e2f0b27032..8ae5818b60 100644 --- a/frontend/app_flowy/assets/translations/pt-BR.json +++ b/frontend/app_flowy/assets/translations/pt-BR.json @@ -7,11 +7,11 @@ "letsGoButtonText": "Vamos lá", "title": "Título", "signUp": { - "buttonText": "Inscreve-se", - "title": "Inscrever-se @:appName", + "buttonText": "Se inscreva", + "title": "Se inscreva no @:appName", "getStartedText": "Começar", - "emptyPasswordError": "Senha não pode ser em branco.", - "repeatPasswordEmptyError": "Confirmar a senha não pode ser em branco.", + "emptyPasswordError": "Senha não pode estar em branco.", + "repeatPasswordEmptyError": "Confirmar a senha não pode estar em branco.", "unmatchedPasswordError": "As senhas não conferem.", "alreadyHaveAnAccount": "Já possui uma conta?", "emailHint": "Email", @@ -19,14 +19,14 @@ "repeatPasswordHint": "Confirme a senha" }, "signIn": { - "loginTitle": "Login to @:appName", + "loginTitle": "Entre no @:appName", "loginButtonText": "Login", "buttonText": "Entre", "forgotPassword": "Esqueceu a senha?", "emailHint": "Email", "passwordHint": "Senha", "dontHaveAnAccount": "Não possui uma conta?", - "repeatPasswordEmptyError": "Confirmar a senha não pode ser em branco.", + "repeatPasswordEmptyError": "Confirmar a senha não pode estar em branco.", "unmatchedPasswordError": "As senhas não conferem." }, "workspace": { @@ -67,7 +67,7 @@ "whatsNew": "O que há de novo?", "help": "Ajuda & Suporte", "debug": { - "name": "Informação de debug", + "name": "Informação de depuração", "success": "Copiar informação de debug para o clipboard!", "fail": "Falha em copiar a informação de debug para o clipboard" } @@ -104,7 +104,7 @@ }, "button": { "OK": "OK", - "Cancel": "Canelar", + "Cancel": "Cancelar", "signIn": "Entrar", "signOut": "Sair", "complete": "Completar", @@ -143,4 +143,5 @@ } } } - \ No newline at end of file + + diff --git a/frontend/app_flowy/assets/translations/pt-PT.json b/frontend/app_flowy/assets/translations/pt-PT.json new file mode 100644 index 0000000000..3aa37ee230 --- /dev/null +++ b/frontend/app_flowy/assets/translations/pt-PT.json @@ -0,0 +1,146 @@ +{ + "appName": "AppFlowy", + "defaultUsername": "Me", + "welcomeText": "Bem vindo ao @:appName", + "githubStarText": "Star on GitHub", + "subscribeNewsletterText": "Inscreve-te ao Newsletter", + "letsGoButtonText": "Bora", + "title": "Título", + "signUp": { + "buttonText": "Inscreve-te", + "title": "Inscreve-te ao @:appName", + "getStartedText": "Começar", + "emptyPasswordError": "A palavra-passe não pode estar em branco.", + "repeatPasswordEmptyError": "Confirmar a palavra-passe não pode estar em branco.", + "unmatchedPasswordError": "As palavras-passes não coincidem.", + "alreadyHaveAnAccount": "Já possuis uma conta?", + "emailHint": "Email", + "passwordHint": "Password", + "repeatPasswordHint": "Confirma a tua password" + }, + "signIn": { + "loginTitle": "Entre no @:appName", + "loginButtonText": "Login", + "buttonText": "Entre", + "forgotPassword": "Esqueceste-te da tua palavra-passe?", + "emailHint": "Email", + "passwordHint": "Palavra-passe", + "dontHaveAnAccount": "Não possuis uma conta?", + "repeatPasswordEmptyError": "Confirmar a palavra-passe não pode estar em branco.", + "unmatchedPasswordError": "As palavras-passes não conferem." + }, + "workspace": { + "create": "Cria um ambiente de trabalho", + "hint": "ambiente de trabalho", + "notFoundError": "Ambiente de trabalho não encontrada" + }, + "shareAction": { + "buttonText": "Partilhar", + "workInProgress": "Em breve", + "markdown": "Markdown", + "copyLink": "Copiar o link" + }, + "disclosureAction": { + "rename": "Renomear", + "delete": "Apagar", + "duplicate": "Duplicar" + }, + "blankPageTitle": "Página em branco", + "newPageText": "Nova página", + "trash": { + "text": "Lixo", + "restoreAll": "Restaurar todos", + "deleteAll": "Apagar todos", + "pageHeader": { + "fileName": "Nome do ficheiro", + "lastModified": "Última modificação", + "created": "Criado" + } + }, + "deletePagePrompt": { + "text": "Esta página está no lixo", + "restore": "Restaurar a página", + "deletePermanent": "Apagar permanentemente" + }, + "dialogCreatePageNameHint": "Nome da página", + "questionBubble": { + "whatsNew": "O que há de novo?", + "help": "Ajuda & Suporte", + "debug": { + "name": "Informação de depuração", + "success": "Copiar informação de depuração para o clipboard!", + "fail": "Falha em copiar a informação de depuração para o clipboard" + } + }, + "menuAppHeader": { + "addPageTooltip": "Adiciona uma nova página.", + "defaultNewPageName": "Sem título", + "renameDialog": "Renomear" + }, + "toolbar": { + "undo": "Desfazer", + "redo": "Refazer", + "bold": "Negrito", + "italic": "Itálico", + "underline": "Sublinhado", + "strike": "Riscado", + "numList": "Lista numerada", + "bulletList": "Lista com marcadores", + "checkList": "Lista de verificação", + "inlineCode": "Embutir código", + "quote": "Citação em bloco", + "header": "Cabeçalho", + "highlight": "Realçar" + }, + "tooltip": { + "lightMode": "Mudar para o modo Claro.", + "darkMode": "Mudar para o modo Escuro." + }, + "contactsPage": { + "title": "Conctatos", + "whatsHappening": "O que está a acontecer nesta semana?", + "addContact": "Adicionar um conctato", + "editContact": "Editar um conctato" + }, + "button": { + "OK": "OK", + "Cancel": "Cancelar", + "signIn": "Entrar", + "signOut": "Sair", + "complete": "Completar", + "save": "Guardar" + }, + "label": { + "welcome": "Bem vindo!", + "firstName": "Nome", + "middleName": "Nome do Meio", + "lastName": "Apelido", + "stepX": "Passo {X}" + }, + "oAuth": { + "err": { + "failedTitle": "Erro ao conectar à sua conta.", + "failedMsg": "Verifica se concluiste o processo de login no teu navegador." + }, + "google": { + "title": "GOOGLE SIGN-IN", + "instruction1": "Para importar os teus Conctatos do Google, tens de autorizar esta aplicação usando o teu navegador web.", + "instruction2": "Copia este código para a tua área de transferências clicando no ícone ou selecionando o texto:", + "instruction3": "Navega até o link a seguir no seu navegador e digite o código acima:", + "instruction4": "Clica no botão abaixo ao concluir a inscrição:" + } + }, + "settings": { + "title": "Definições", + "menu": { + "appearance": "Aparência", + "language": "Idioma", + "open": "Abrir as Definições" + }, + "appearance": { + "lightLabel": "Modo Claro", + "darkLabel": "Modo Escuro" + } + } + } + diff --git a/frontend/app_flowy/assets/translations/ru-RU.json b/frontend/app_flowy/assets/translations/ru-RU.json index d729c0a737..65e61347e6 100644 --- a/frontend/app_flowy/assets/translations/ru-RU.json +++ b/frontend/app_flowy/assets/translations/ru-RU.json @@ -141,6 +141,68 @@ "lightLabel": "Светлая тема", "darkLabel": "Тёмная тема" } + }, + "grid": { + "settings": { + "filter": "Фильтр", + "sortBy": "Сортировать", + "Properties": "Свойства" + }, + "field": { + "hide": "Скрыть", + "insertLeft": "Вставить слева", + "insertRight": "Вставить справа", + "duplicate": "Дублировать", + "delete": "Удалить", + "textFieldName": "Текст", + "checkboxFieldName": "Checkbox", + "dateFieldName": "Дата", + "numberFieldName": "Число", + "singleSelectFieldName": "Выбор", + "multiSelectFieldName": "Выбор многих", + "urlFieldName": "URL", + "numberFormat": " Формат числа", + "dateFormat": " Формат даты", + "includeTime": " Время", + "dateFormatFriendly": "День Месяц, Год", + "dateFormatISO": "Год-Месяц-День", + "dateFormatLocal": "Год/Месяц/День", + "dateFormatUS": "Год/Месяц/День", + "timeFormat": " Форматировать время", + "invalidTimeFormat": "Неверный формат", + "timeFormatTwelveHour": "12 часов", + "timeFormatTwentyFourHour": "24 часа", + "addSelectOption": "Добавить вариант", + "optionTitle": "Варианты", + "addOption": "Добавить", + "editProperty": "Редактировать свойство" + }, + "row": { + "duplicate": "Дублировать", + "delete": "Удалить", + "textPlaceholder": "Пусто", + "copyProperty": "Свойство скопировано" + }, + "selectOption": { + "create": "Создать", + "purpleColor": "Фиолетовый", + "pinkColor": "Розовый", + "lightPinkColor": "Светло-розовый", + "orangeColor": "Оранжевый", + "yellowColor": "Желтый", + "limeColor": "Ярко-зелёный", + "greenColor": "Зелёный", + "aquaColor": "Морской волны", + "blueColor": "Синий", + "deleteTag": "Удалить вариант", + "colorPannelTitle": "Цвета", + "pannelTitle": "Выберите или создайте вариант", + "searchOption": "Поиск" + }, + "date": { + "timeHintTextInTwelveHour": "12:00 AM", + "timeHintTextInTwentyFourHour": "12:00" + } } } \ No newline at end of file diff --git a/frontend/app_flowy/lib/startup/deps_resolver.dart b/frontend/app_flowy/lib/startup/deps_resolver.dart index 9be9420fb5..b378d6ee7b 100644 --- a/frontend/app_flowy/lib/startup/deps_resolver.dart +++ b/frontend/app_flowy/lib/startup/deps_resolver.dart @@ -16,6 +16,7 @@ import 'package:app_flowy/workspace/presentation/home/menu/menu.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:get_it/get_it.dart'; class DependencyResolver { @@ -46,6 +47,8 @@ void _resolveUserDeps(GetIt getIt) { } void _resolveHomeDeps(GetIt getIt) { + getIt.registerSingleton(FToast()); + getIt.registerSingleton(MenuSharedState()); getIt.registerFactoryParam( diff --git a/frontend/app_flowy/lib/startup/tasks/app_widget.dart b/frontend/app_flowy/lib/startup/tasks/app_widget.dart index 1747cfd8ec..9961142c6d 100644 --- a/frontend/app_flowy/lib/startup/tasks/app_widget.dart +++ b/frontend/app_flowy/lib/startup/tasks/app_widget.dart @@ -67,40 +67,42 @@ class ApplicationWidget extends StatelessWidget { }) : super(key: key); @override - Widget build(BuildContext context) => ChangeNotifierProvider.value( - value: settingModel, - builder: (context, _) { - const ratio = 1.73; - const minWidth = 600.0; - setWindowMinSize(const Size(minWidth, minWidth / ratio)); - settingModel.readLocaleWhenAppLaunch(context); - AppTheme theme = context.select( - (value) => value.theme, - ); - Locale locale = context.select( - (value) => value.locale, - ); + Widget build(BuildContext context) { + return ChangeNotifierProvider.value( + value: settingModel, + builder: (context, _) { + const ratio = 1.73; + const minWidth = 600.0; + setWindowMinSize(const Size(minWidth, minWidth / ratio)); + settingModel.readLocaleWhenAppLaunch(context); + AppTheme theme = context.select( + (value) => value.theme, + ); + Locale locale = context.select( + (value) => value.locale, + ); - return MultiProvider( - providers: [ - Provider.value(value: theme), - Provider.value(value: locale), - ], - builder: (context, _) { - return MaterialApp( - builder: overlayManagerBuilder(), - debugShowCheckedModeBanner: false, - theme: theme.themeData, - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: locale, - navigatorKey: AppGlobals.rootNavKey, - home: child, - ); - }, - ); - }, - ); + return MultiProvider( + providers: [ + Provider.value(value: theme), + Provider.value(value: locale), + ], + builder: (context, _) { + return MaterialApp( + builder: overlayManagerBuilder(), + debugShowCheckedModeBanner: false, + theme: theme.themeData, + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: locale, + navigatorKey: AppGlobals.rootNavKey, + home: child, + ); + }, + ); + }, + ); + } } class AppGlobals { diff --git a/frontend/app_flowy/lib/workspace/application/doc/doc_service.dart b/frontend/app_flowy/lib/workspace/application/doc/doc_service.dart index a0498491f9..6ad08814f4 100644 --- a/frontend/app_flowy/lib/workspace/application/doc/doc_service.dart +++ b/frontend/app_flowy/lib/workspace/application/doc/doc_service.dart @@ -12,14 +12,14 @@ class DocumentService { await FolderEventSetLatestView(ViewId(value: docId)).send(); final payload = TextBlockId(value: docId); - return BlockEventGetBlockData(payload).send(); + return TextBlockEventGetBlockData(payload).send(); } Future> composeDelta({required String docId, required String data}) { final payload = TextBlockDelta.create() ..blockId = docId ..deltaStr = data; - return BlockEventApplyDelta(payload).send(); + return TextBlockEventApplyDelta(payload).send(); } Future> closeDocument({required String docId}) { diff --git a/frontend/app_flowy/lib/workspace/application/doc/share_service.dart b/frontend/app_flowy/lib/workspace/application/doc/share_service.dart index cc3afe1314..7e5545f109 100644 --- a/frontend/app_flowy/lib/workspace/application/doc/share_service.dart +++ b/frontend/app_flowy/lib/workspace/application/doc/share_service.dart @@ -10,7 +10,7 @@ class ShareService { ..viewId = docId ..exportType = type; - return BlockEventExportDocument(request).send(); + return TextBlockEventExportDocument(request).send(); } Future> exportText(String docId) { 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 e4141c3e16..3041c563d9 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 @@ -2,7 +2,7 @@ part of 'cell_service.dart'; typedef GridCellContext = _GridCellContext; typedef GridSelectOptionCellContext = _GridCellContext; -typedef GridDateCellContext = _GridCellContext; +typedef GridDateCellContext = _GridCellContext; typedef GridURLCellContext = _GridCellContext; class GridCellContextBuilder { @@ -31,6 +31,7 @@ class GridCellContextBuilder { final cellDataLoader = GridCellDataLoader( gridCell: _gridCell, parser: DateCellDataParser(), + config: const GridCellDataConfig(reloadOnFieldChanged: true), ); return GridDateCellContext( @@ -105,7 +106,7 @@ class _GridCellContext extends Equatable { final FieldService _fieldService; late final CellListener _cellListener; - late final ValueNotifier _cellDataNotifier; + late final ValueNotifier? _cellDataNotifier; bool isListening = false; VoidCallback? _onFieldChangedFn; Timer? _loadDataOperation; @@ -163,19 +164,19 @@ class _GridCellContext extends Equatable { } onCellChangedFn() { - onCellChanged(_cellDataNotifier.value); + onCellChanged(_cellDataNotifier?.value); if (cellDataLoader.config.reloadOnCellChanged) { _loadData(); } } - _cellDataNotifier.addListener(onCellChangedFn); + _cellDataNotifier?.addListener(onCellChangedFn); return onCellChangedFn; } void removeListener(VoidCallback fn) { - _cellDataNotifier.removeListener(fn); + _cellDataNotifier?.removeListener(fn); } T? getCellData({bool loadIfNoCache = true}) { @@ -211,13 +212,14 @@ class _GridCellContext extends Equatable { _loadDataOperation?.cancel(); _loadDataOperation = Timer(const Duration(milliseconds: 10), () { cellDataLoader.loadData().then((data) { - _cellDataNotifier.value = data; + _cellDataNotifier?.value = data; cellCache.insert(GridCellCacheData(key: _cacheKey, object: data)); }); }); } void dispose() { + _cellListener.stop(); _loadDataOperation?.cancel(); _saveDataOperation?.cancel(); 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 92caedc4e9..676e3f66d0 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 @@ -58,11 +58,7 @@ class GridCellDataLoader extends IGridCellDataLoader { return fut.then( (result) => result.fold((Cell cell) { try { - if (cell.data.isEmpty) { - return null; - } else { - return parser.parserData(cell.data); - } + return parser.parserData(cell.data); } catch (e, s) { Log.error('$parser parser cellData failed, $e'); Log.error('Stack trace \n $s'); @@ -102,13 +98,17 @@ class SelectOptionCellDataLoader extends IGridCellDataLoader { @override String? parserData(List data) { - return utf8.decode(data); + final s = utf8.decode(data); + return s; } } class DateCellDataParser implements ICellDataParser { @override DateCellData? parserData(List data) { + if (data.isEmpty) { + return null; + } return DateCellData.fromBuffer(data); } } @@ -116,6 +116,9 @@ class DateCellDataParser implements ICellDataParser { class SelectOptionCellDataParser implements ICellDataParser { @override SelectOptionCellData? parserData(List data) { + if (data.isEmpty) { + return null; + } return SelectOptionCellData.fromBuffer(data); } } @@ -123,6 +126,9 @@ class SelectOptionCellDataParser implements ICellDataParser { @override URLCellData? parserData(List data) { + if (data.isEmpty) { + return null; + } return URLCellData.fromBuffer(data); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_persistence.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_persistence.dart index e09a528e44..2ad217e062 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_persistence.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_persistence.dart @@ -31,18 +31,18 @@ class CellDataPersistence implements _GridCellDataPersistence { } @freezed -class DateCalData with _$DateCalData { - const factory DateCalData({required DateTime date, String? time}) = _DateCellPersistenceData; +class CalendarData with _$CalendarData { + const factory CalendarData({required DateTime date, String? time}) = _CalendarData; } -class DateCellDataPersistence implements _GridCellDataPersistence { +class DateCellDataPersistence implements _GridCellDataPersistence { final GridCell gridCell; DateCellDataPersistence({ required this.gridCell, }); @override - Future> save(DateCalData data) { + Future> save(CalendarData data) { var payload = DateChangesetPayload.create()..cellIdentifier = _cellIdentifier(gridCell); final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString(); 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 15f18707f8..c72a10b481 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 @@ -38,9 +38,9 @@ class DateCalBloc extends Bloc { emit(state.copyWith(focusedDay: focusedDay)); }, didReceiveCellUpdate: (DateCellData? cellData) { - final dateData = dateDataFromCellData(cellData); - final time = dateData.foldRight("", (dateData, previous) => dateData.time); - emit(state.copyWith(dateData: dateData, time: time)); + final calData = calDataFromCellData(cellData); + final time = calData.foldRight("", (dateData, previous) => dateData.time); + emit(state.copyWith(calData: calData, time: time)); }, setIncludeTime: (includeTime) async { await _updateTypeOption(emit, includeTime: includeTime); @@ -52,7 +52,12 @@ class DateCalBloc extends Bloc { await _updateTypeOption(emit, timeFormat: timeFormat); }, setTime: (time) async { - await _updateDateData(emit, time: time); + if (state.calData.isSome()) { + await _updateDateData(emit, time: time); + } + }, + didUpdateCalData: (Option data, Option timeFormatError) { + emit(state.copyWith(calData: data, timeFormatError: timeFormatError)); }, ); }, @@ -60,8 +65,8 @@ class DateCalBloc extends Bloc { } Future _updateDateData(Emitter emit, {DateTime? date, String? time}) { - final DateCalData newDateData = state.dateData.fold( - () => DateCalData(date: date ?? DateTime.now(), time: time), + final CalendarData newDateData = state.calData.fold( + () => CalendarData(date: date ?? DateTime.now(), time: time), (dateData) { var newDateData = dateData; if (date != null && !isSameDay(newDateData.date, date)) { @@ -78,24 +83,22 @@ class DateCalBloc extends Bloc { return _saveDateData(emit, newDateData); } - Future _saveDateData(Emitter emit, DateCalData newDateData) async { - if (state.dateData == Some(newDateData)) { + Future _saveDateData(Emitter emit, CalendarData newCalData) async { + if (state.calData == Some(newCalData)) { return; } - cellContext.saveCellData(newDateData, resultCallback: (result) { + updateCalData(Option calData, Option timeFormatError) { + if (!isClosed) add(DateCalEvent.didUpdateCalData(calData, timeFormatError)); + } + + cellContext.saveCellData(newCalData, resultCallback: (result) { result.fold( - () => emit(state.copyWith( - dateData: Some(newDateData), - timeFormatError: none(), - )), + () => updateCalData(Some(newCalData), none()), (err) { switch (ErrorCode.valueOf(err.code)!) { case ErrorCode.InvalidDateTimeFormat: - emit(state.copyWith( - dateData: Some(newDateData), - timeFormatError: Some(timeFormatPrompt(err)), - )); + updateCalData(none(), Some(timeFormatPrompt(err))); break; default: Log.error(err); @@ -168,7 +171,7 @@ class DateCalBloc extends Bloc { ); result.fold( - (l) => emit(state.copyWith(dateTypeOption: newDateTypeOption)), + (l) => emit(state.copyWith(dateTypeOption: newDateTypeOption, timeHintText: _timeHintText(newDateTypeOption))), (err) => Log.error(err), ); } @@ -185,6 +188,8 @@ class DateCalEvent with _$DateCalEvent { const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime; const factory DateCalEvent.setTime(String time) = _Time; const factory DateCalEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate; + const factory DateCalEvent.didUpdateCalData(Option data, Option timeFormatError) = + _DidUpdateCalData; } @freezed @@ -194,36 +199,48 @@ class DateCalState with _$DateCalState { required CalendarFormat format, required DateTime focusedDay, required Option timeFormatError, - required Option dateData, + required Option calData, required String? time, + required String timeHintText, }) = _DateCalState; factory DateCalState.initial( DateTypeOption dateTypeOption, DateCellData? cellData, ) { - Option dateData = dateDataFromCellData(cellData); - final time = dateData.foldRight("", (dateData, previous) => dateData.time); + Option calData = calDataFromCellData(cellData); + final time = calData.foldRight("", (dateData, previous) => dateData.time); return DateCalState( dateTypeOption: dateTypeOption, format: CalendarFormat.month, focusedDay: DateTime.now(), time: time, - dateData: dateData, + calData: calData, timeFormatError: none(), + timeHintText: _timeHintText(dateTypeOption), ); } } -Option dateDataFromCellData(DateCellData? cellData) { +String _timeHintText(DateTypeOption typeOption) { + switch (typeOption.timeFormat) { + case TimeFormat.TwelveHour: + return LocaleKeys.grid_date_timeHintTextInTwelveHour.tr(); + case TimeFormat.TwentyFourHour: + return LocaleKeys.grid_date_timeHintTextInTwentyFourHour.tr(); + } + return ""; +} + +Option calDataFromCellData(DateCellData? cellData) { String? time = timeFromCellData(cellData); - Option dateData = none(); + Option calData = none(); if (cellData != null) { final timestamp = cellData.timestamp * 1000; final date = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt()); - dateData = Some(DateCalData(date: date, time: time)); + calData = Some(CalendarData(date: date, time: time)); } - return dateData; + return calData; } $fixnum.Int64 timestampFromDateTime(DateTime dateTime) { 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 b06a3d60b3..b9f4c74070 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 @@ -4,7 +4,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; import 'cell_service/cell_service.dart'; -import 'package:dartz/dartz.dart'; part 'date_cell_bloc.freezed.dart'; class DateCellBloc extends Bloc { @@ -17,11 +16,7 @@ class DateCellBloc extends Bloc { event.when( initial: () => _startListening(), didReceiveCellUpdate: (DateCellData? cellData) { - if (cellData != null) { - emit(state.copyWith(data: Some(cellData))); - } else { - emit(state.copyWith(data: none())); - } + emit(state.copyWith(data: cellData, dateStr: _dateStrFromCellData(cellData))); }, didReceiveFieldUpdate: (Field value) => emit(state.copyWith(field: value)), ); @@ -60,21 +55,26 @@ class DateCellEvent with _$DateCellEvent { @freezed class DateCellState with _$DateCellState { const factory DateCellState({ - required Option data, + required DateCellData? data, + required String dateStr, required Field field, }) = _DateCellState; factory DateCellState.initial(GridDateCellContext context) { final cellData = context.getCellData(); - Option data = none(); - - if (cellData != null) { - data = Some(cellData); - } return DateCellState( field: context.field, - data: data, + data: cellData, + dateStr: _dateStrFromCellData(cellData), ); } } + +String _dateStrFromCellData(DateCellData? cellData) { + String dateStr = ""; + if (cellData != null) { + dateStr = cellData.date + " " + cellData.time; + } + return dateStr; +} 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 8157f6a3f2..adcfee71e6 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,6 +1,8 @@ +import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; +import 'package:dartz/dartz.dart'; import 'cell_service/cell_service.dart'; part 'number_cell_bloc.freezed.dart'; @@ -14,25 +16,28 @@ class NumberCellBloc extends Bloc { }) : super(NumberCellState.initial(cellContext)) { on( (event, emit) async { - await event.map( - initial: (_Initial value) async { + event.when( + initial: () { _startListening(); }, - didReceiveCellUpdate: (_DidReceiveCellUpdate value) { - emit(state.copyWith(content: value.cellContent ?? "")); + didReceiveCellUpdate: (content) { + emit(state.copyWith(content: content)); }, - updateCell: (_UpdateCell value) async { - await _updateCellValue(value, emit); + updateCell: (text) { + cellContext.saveCellData(text, resultCallback: (result) { + result.fold( + () => null, + (err) { + if (!isClosed) add(NumberCellEvent.didReceiveCellUpdate(right(err))); + }, + ); + }); }, ); }, ); } - Future _updateCellValue(_UpdateCell value, Emitter emit) async { - cellContext.saveCellData(value.text); - } - @override Future close() async { if (_onCellChangedFn != null) { @@ -47,7 +52,7 @@ class NumberCellBloc extends Bloc { _onCellChangedFn = cellContext.startListening( onCellChanged: ((cellContent) { if (!isClosed) { - add(NumberCellEvent.didReceiveCellUpdate(cellContent)); + add(NumberCellEvent.didReceiveCellUpdate(left(cellContent ?? ""))); } }), ); @@ -58,17 +63,19 @@ class NumberCellBloc extends Bloc { class NumberCellEvent with _$NumberCellEvent { const factory NumberCellEvent.initial() = _Initial; const factory NumberCellEvent.updateCell(String text) = _UpdateCell; - const factory NumberCellEvent.didReceiveCellUpdate(String? cellContent) = _DidReceiveCellUpdate; + const factory NumberCellEvent.didReceiveCellUpdate(Either cellContent) = _DidReceiveCellUpdate; } @freezed class NumberCellState with _$NumberCellState { const factory NumberCellState({ - required String content, + required Either content, }) = _NumberCellState; factory NumberCellState.initial(GridCellContext context) { final cellContent = context.getCellData() ?? ""; - return NumberCellState(content: cellContent); + return NumberCellState( + content: left(cellContent), + ); } } 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 index 609c625001..e1fe39c3bf 100644 --- 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 @@ -24,6 +24,9 @@ class URLCellBloc extends Bloc { url: cellData?.url ?? "", )); }, + updateURL: (String url) { + cellContext.saveCellData(url, deduplicate: true); + }, ); }, ); @@ -53,6 +56,7 @@ class URLCellBloc extends Bloc { @freezed class URLCellEvent with _$URLCellEvent { const factory URLCellEvent.initial() = _InitialCell; + const factory URLCellEvent.updateURL(String url) = _UpdateURL; const factory URLCellEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate; } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart index 06a95ae89b..c7e83cc52e 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart @@ -24,13 +24,15 @@ class FieldCellBloc extends Bloc { _startListening(); }, didReceiveFieldUpdate: (field) { - emit(state.copyWith(field: field)); + emit(state.copyWith(field: cellContext.field)); }, - updateWidth: (offset) { - final defaultWidth = state.field.width.toDouble(); - final width = defaultWidth + offset; - if (width > defaultWidth && width < 300) { - _fieldService.updateField(width: width); + startUpdateWidth: (offset) { + final width = state.width + offset; + emit(state.copyWith(width: width)); + }, + endUpdateWidth: () { + if (state.width != state.field.width.toDouble()) { + _fieldService.updateField(width: state.width); } }, ); @@ -61,7 +63,8 @@ class FieldCellBloc extends Bloc { class FieldCellEvent with _$FieldCellEvent { const factory FieldCellEvent.initial() = _InitialCell; const factory FieldCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate; - const factory FieldCellEvent.updateWidth(double offset) = _UpdateWidth; + const factory FieldCellEvent.startUpdateWidth(double offset) = _StartUpdateWidth; + const factory FieldCellEvent.endUpdateWidth() = _EndUpdateWidth; } @freezed @@ -69,10 +72,12 @@ class FieldCellState with _$FieldCellState { const factory FieldCellState({ required String gridId, required Field field, + required double width, }) = _FieldCellState; factory FieldCellState.initial(GridFieldCellContext cellContext) => FieldCellState( gridId: cellContext.gridId, field: cellContext.field, + width: cellContext.field.width.toDouble(), ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart index 951bdb9261..a708668066 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart @@ -1,4 +1,5 @@ import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/format.pbenum.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_format_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_format_bloc.dart index 74f1531ce3..a0a853913c 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_format_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_format_bloc.dart @@ -1,4 +1,4 @@ -import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/format.pbenum.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart index 357ccadcd9..16b4beb041 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:dartz/dartz.dart'; +import 'package:equatable/equatable.dart'; 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/protobuf.dart'; @@ -8,6 +9,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'cell/cell_service/cell_service.dart'; import 'grid_service.dart'; import 'row/row_service.dart'; +import 'dart:collection'; part 'grid_bloc.freezed.dart'; @@ -33,19 +35,19 @@ class GridBloc extends Bloc { on( (event, emit) async { - await event.map( - initial: (InitialGrid value) async { + await event.when( + initial: () async { _startListening(); await _loadGrid(emit); }, - createRow: (_CreateRow value) { + createRow: () { _gridService.createRow(); }, - didReceiveRowUpdate: (_DidReceiveRowUpdate value) { - emit(state.copyWith(rows: value.rows, listState: value.listState)); + didReceiveRowUpdate: (rows, listState) { + emit(state.copyWith(rows: rows, listState: listState)); }, - didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) { - emit(state.copyWith(rows: rowCache.clonedRows, fields: value.fields)); + didReceiveFieldUpdate: (fields) { + emit(state.copyWith(rows: rowCache.clonedRows, fields: GridFieldEquatable(fields))); }, ); }, @@ -93,7 +95,7 @@ class GridBloc extends Bloc { emit(state.copyWith( grid: Some(grid), - fields: fieldCache.fields, + fields: GridFieldEquatable(fieldCache.fields), rows: rowCache.clonedRows, loadingState: GridLoadingState.finish(left(unit)), )); @@ -117,14 +119,14 @@ class GridState with _$GridState { const factory GridState({ required String gridId, required Option grid, - required List fields, + required GridFieldEquatable fields, required List rows, required GridLoadingState loadingState, required GridRowChangeReason listState, }) = _GridState; factory GridState.initial(String gridId) => GridState( - fields: [], + fields: const GridFieldEquatable([]), rows: [], grid: none(), gridId: gridId, @@ -138,3 +140,19 @@ class GridLoadingState with _$GridLoadingState { const factory GridLoadingState.loading() = _Loading; const factory GridLoadingState.finish(Either successOrFail) = _Finish; } + +class GridFieldEquatable extends Equatable { + final List _fields; + + const GridFieldEquatable(List fields) : _fields = fields; + + @override + List get props { + return [ + _fields.length, + _fields.map((field) => field.width).reduce((value, element) => value + element), + ]; + } + + UnmodifiableListView get value => UnmodifiableListView(_fields); +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart index 3d3e3486a5..d8000e3664 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart @@ -30,7 +30,7 @@ class RowBloc extends Bloc { _rowService.createRow(); }, didReceiveCellDatas: (_DidReceiveCellDatas value) async { - final fields = value.gridCellMap.values.map((e) => CellSnapshot(e.field)).toList(); + final fields = value.gridCellMap.values.map((e) => GridCellEquatable(e.field)).toList(); final snapshots = UnmodifiableListView(fields); emit(state.copyWith( gridCellMap: value.gridCellMap, @@ -74,26 +74,27 @@ class RowState with _$RowState { const factory RowState({ required GridRow rowData, required GridCellMap gridCellMap, - required UnmodifiableListView snapshots, + required UnmodifiableListView snapshots, GridRowChangeReason? changeReason, }) = _RowState; factory RowState.initial(GridRow rowData, GridCellMap cellDataMap) => RowState( rowData: rowData, gridCellMap: cellDataMap, - snapshots: UnmodifiableListView(cellDataMap.values.map((e) => CellSnapshot(e.field)).toList()), + snapshots: UnmodifiableListView(cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList()), ); } -class CellSnapshot extends Equatable { +class GridCellEquatable extends Equatable { final Field _field; - const CellSnapshot(Field field) : _field = field; + const GridCellEquatable(Field field) : _field = field; @override List get props => [ _field.id, _field.fieldType, _field.visibility, + _field.width, ]; } diff --git a/frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart b/frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart index 86fa9fab79..7e6d4abba2 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart @@ -18,7 +18,6 @@ import 'home_stack.dart'; import 'menu/menu.dart'; class HomeScreen extends StatefulWidget { - static GlobalKey scaffoldKey = GlobalKey(); final UserProfile user; final CurrentWorkspaceSetting workspaceSetting; const HomeScreen(this.user, this.workspaceSetting, {Key? key}) : super(key: key); @@ -52,7 +51,6 @@ class _HomeScreenState extends State { ), ], child: Scaffold( - key: HomeScreen.scaffoldKey, body: BlocListener( listenWhen: (p, c) => p.unauthorized != c.unauthorized, listener: (context, state) { 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 c16c965a82..07bd99deb1 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart @@ -2,15 +2,13 @@ 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:app_flowy/workspace/presentation/home/toast.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'; - 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'; @@ -22,11 +20,7 @@ import 'package:flowy_infra/notifier.dart'; typedef NavigationCallback = void Function(String id); -late FToast fToast; - class HomeStack extends StatelessWidget { - static GlobalKey scaffoldKey = GlobalKey(); - // final Size size; const HomeStack({Key? key}) : super(key: key); @override @@ -74,8 +68,7 @@ class _FadingIndexedStackState extends State { @override void initState() { super.initState(); - fToast = FToast(); - fToast.init(HomeScreen.scaffoldKey.currentState!.context); + initToastWithContext(context); } @override diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/add_button.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/add_button.dart index a7de233006..39c13c1c12 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/add_button.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/add_button.dart @@ -28,7 +28,7 @@ class AddButton extends StatelessWidget { onSelected: onSelected, ).show(context); }, - icon: svgWidget("home/add").padding(horizontal: 3, vertical: 3), + icon: svgWidget("home/add", color: theme.iconColor).padding(horizontal: 3, vertical: 3), ); } } @@ -46,8 +46,8 @@ class ActionList { return CreateItem( pluginBuilder: pluginBuilder, onSelected: (builder) { - FlowyOverlay.of(buildContext).remove(_identifier); onSelected(builder); + FlowyOverlay.of(buildContext).remove(_identifier); }, ); }, diff --git a/frontend/app_flowy/lib/workspace/presentation/home/toast.dart b/frontend/app_flowy/lib/workspace/presentation/home/toast.dart new file mode 100644 index 0000000000..28241c0ec4 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/home/toast.dart @@ -0,0 +1,37 @@ +import 'package:app_flowy/startup/startup.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +class FlowyMessageToast extends StatelessWidget { + final String message; + const FlowyMessageToast({required this.message, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + child: FlowyText.medium(message, color: Colors.white), + ), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(4)), + color: Colors.black, + ), + ); + } +} + +void initToastWithContext(BuildContext context) { + getIt().init(context); +} + +void showMessageToast(String message) { + final child = FlowyMessageToast(message: message); + + getIt().showToast( + child: child, + gravity: ToastGravity.BOTTOM, + toastDuration: const Duration(seconds: 3), + ); +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/toolbar_icon_button.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/toolbar_icon_button.dart index b6dddefbb3..aac5b5a4b1 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/toolbar_icon_button.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/toolbar_icon_button.dart @@ -29,9 +29,9 @@ class ToolbarIconButton extends StatelessWidget { iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4), onPressed: onPressed, width: width, - icon: isToggled == true ? svgWidget(iconName, color: Colors.white) : svgWidget(iconName), + icon: isToggled == true ? svgWidget(iconName, color: Colors.white) : svgWidget(iconName, color: theme.iconColor), fillColor: isToggled == true ? theme.main1 : theme.shader6, - hoverColor: isToggled == true ? theme.main1 : theme.shader5, + hoverColor: isToggled == true ? theme.main1 : theme.hover, tooltipText: tooltipText, ); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart index 5a913137af..19c94817d8 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart @@ -15,6 +15,7 @@ import 'layout/sizes.dart'; import 'widgets/row/grid_row.dart'; import 'widgets/footer/grid_footer.dart'; import 'widgets/header/grid_header.dart'; +import 'widgets/shortcuts.dart'; import 'widgets/toolbar/grid_toolbar.dart'; class GridPage extends StatefulWidget { @@ -40,7 +41,7 @@ class _GridPageState extends State { return state.loadingState.map( loading: (_) => const Center(child: CircularProgressIndicator.adaptive()), finish: (result) => result.successOrFail.fold( - (_) => const FlowyGrid(), + (_) => const GridShortcuts(child: FlowyGrid()), (err) => FlowyErrorPage(err.toString()), ), ); @@ -91,9 +92,9 @@ class _FlowyGridState extends State { @override Widget build(BuildContext context) { return BlocBuilder( - buildWhen: (previous, current) => previous.fields.length != current.fields.length, + buildWhen: (previous, current) => previous.fields != current.fields, builder: (context, state) { - final contentWidth = GridLayout.headerWidth(state.fields); + final contentWidth = GridLayout.headerWidth(state.fields.value); final child = _wrapScrollView( contentWidth, [ diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart new file mode 100644 index 0000000000..88eadb39c4 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart @@ -0,0 +1,198 @@ +import 'package:flowy_infra/image.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flowy_infra/theme.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:flowy_infra/size.dart'; +import 'package:styled_widget/styled_widget.dart'; + +class GridCellAccessoryBuildContext { + final BuildContext anchorContext; + final bool isCellEditing; + + GridCellAccessoryBuildContext({ + required this.anchorContext, + required this.isCellEditing, + }); +} + +abstract class GridCellAccessory implements Widget { + void onTap(); + + // The accessory will be hidden if enable() return false; + bool enable() => true; +} + +class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory { + final VoidCallback onTapCallback; + final bool isCellEditing; + const PrimaryCellAccessory({ + required this.onTapCallback, + required this.isCellEditing, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + if (isCellEditing) { + return const SizedBox(); + } else { + final theme = context.watch(); + return svgWidget("grid/expander", color: theme.main1); + } + } + + @override + void onTap() => onTapCallback(); + + @override + bool enable() => !isCellEditing; +} + +typedef AccessoryBuilder = List Function(GridCellAccessoryBuildContext buildContext); + +abstract class CellAccessory extends Widget { + const CellAccessory({Key? key}) : super(key: key); + + // The hover will show if the isHover's value is true + ValueNotifier? get onAccessoryHover; + + AccessoryBuilder? get accessoryBuilder; +} + +class AccessoryHover extends StatefulWidget { + final CellAccessory child; + final EdgeInsets contentPadding; + const AccessoryHover({ + required this.child, + this.contentPadding = EdgeInsets.zero, + Key? key, + }) : super(key: key); + + @override + State createState() => _AccessoryHoverState(); +} + +class _AccessoryHoverState extends State { + late AccessoryHoverState _hoverState; + VoidCallback? _listenerFn; + + @override + void initState() { + _hoverState = AccessoryHoverState(); + _listenerFn = () => _hoverState.onHover = widget.child.onAccessoryHover?.value ?? false; + widget.child.onAccessoryHover?.addListener(_listenerFn!); + + super.initState(); + } + + @override + void dispose() { + _hoverState.dispose(); + + if (_listenerFn != null) { + widget.child.onAccessoryHover?.removeListener(_listenerFn!); + _listenerFn = null; + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + List children = [ + const _Background(), + Padding(padding: widget.contentPadding, child: widget.child), + ]; + + final accessoryBuilder = widget.child.accessoryBuilder; + if (accessoryBuilder != null) { + final accessories = accessoryBuilder((GridCellAccessoryBuildContext( + anchorContext: context, + isCellEditing: false, + ))); + children.add( + Padding( + padding: const EdgeInsets.only(right: 6), + child: CellAccessoryContainer(accessories: accessories), + ).positioned(right: 0), + ); + } + + return ChangeNotifierProvider.value( + value: _hoverState, + child: MouseRegion( + cursor: SystemMouseCursors.click, + opaque: false, + onEnter: (p) => setState(() => _hoverState.onHover = true), + onExit: (p) => setState(() => _hoverState.onHover = false), + child: Stack( + fit: StackFit.loose, + alignment: AlignmentDirectional.center, + children: children, + ), + ), + ); + } +} + +class AccessoryHoverState extends ChangeNotifier { + bool _onHover = false; + + set onHover(bool value) { + if (_onHover != value) { + _onHover = value; + notifyListeners(); + } + } + + bool get onHover => _onHover; +} + +class _Background extends StatelessWidget { + const _Background({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return Consumer( + builder: (context, state, child) { + if (state.onHover) { + return FlowyHoverContainer( + style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: theme.shader6), + ); + } else { + return const SizedBox(); + } + }, + ); + } +} + +class CellAccessoryContainer extends StatelessWidget { + final List accessories; + const CellAccessoryContainer({required this.accessories, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + final children = accessories.where((accessory) => accessory.enable()).map((accessory) { + final hover = FlowyHover( + style: HoverStyle(hoverColor: theme.bg3, backgroundColor: theme.surface), + builder: (_, onHover) => Container( + width: 26, + height: 26, + padding: const EdgeInsets.all(3), + child: accessory, + ), + ); + return GestureDetector( + child: hover, + behavior: HitTestBehavior.opaque, + onTap: () => accessory.onTap(), + ); + }).toList(); + + return Wrap(children: children, spacing: 6); + } +} 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 f8189e7f02..7147272038 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 @@ -1,13 +1,10 @@ import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; -import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType; +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; -import 'package:styled_widget/styled_widget.dart'; +import 'cell_accessory.dart'; +import 'cell_shortcuts.dart'; import 'checkbox_cell.dart'; import 'date_cell/date_cell.dart'; import 'number_cell.dart'; @@ -48,24 +45,132 @@ class BlankCell extends StatelessWidget { } } -abstract class GridCellWidget implements FlowyHoverWidget { - @override - final ValueNotifier onFocus = ValueNotifier(false); +abstract class CellEditable { + GridCellFocusListener get beginFocus; - final GridCellRequestFocusNotifier requestFocus = GridCellRequestFocusNotifier(); + ValueNotifier get onCellFocus; + + ValueNotifier get onCellEditing; } -class GridCellRequestFocusNotifier extends ChangeNotifier { - VoidCallback? _listener; +abstract class GridCellWidget extends StatefulWidget implements CellAccessory, CellEditable, CellShortcuts { + GridCellWidget({Key? key}) : super(key: key) { + onCellEditing.addListener(() { + onCellFocus.value = onCellEditing.value; + }); + } @override - void addListener(VoidCallback listener) { + final ValueNotifier onCellFocus = ValueNotifier(false); + + // When the cell is focused, we assume that the accessory alse be hovered. + @override + ValueNotifier get onAccessoryHover => onCellFocus; + + @override + final ValueNotifier onCellEditing = ValueNotifier(false); + + @override + List Function(GridCellAccessoryBuildContext buildContext)? get accessoryBuilder => null; + + @override + final GridCellFocusListener beginFocus = GridCellFocusListener(); + + @override + final Map shortcutHandlers = {}; +} + +abstract class GridCellState extends State { + @override + void initState() { + widget.beginFocus.setListener(() => requestBeginFocus()); + widget.shortcutHandlers[CellKeyboardKey.onCopy] = () => onCopy(); + widget.shortcutHandlers[CellKeyboardKey.onInsert] = () { + Clipboard.getData("text/plain").then((data) { + final s = data?.text; + if (s is String) { + onInsert(s); + } + }); + }; + super.initState(); + } + + @override + void didUpdateWidget(covariant T oldWidget) { + if (oldWidget != this) { + widget.beginFocus.setListener(() => requestBeginFocus()); + } + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + widget.beginFocus.removeAllListener(); + super.dispose(); + } + + void requestBeginFocus(); + + String? onCopy() => null; + + void onInsert(String value) {} +} + +abstract class GridFocusNodeCellState extends GridCellState { + SingleListenrFocusNode focusNode = SingleListenrFocusNode(); + + @override + void initState() { + widget.shortcutHandlers[CellKeyboardKey.onEnter] = () => focusNode.unfocus(); + _listenOnFocusNodeChanged(); + super.initState(); + } + + @override + void didUpdateWidget(covariant T oldWidget) { + if (oldWidget != this) { + _listenOnFocusNodeChanged(); + } + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + widget.shortcutHandlers.clear(); + focusNode.removeAllListener(); + focusNode.dispose(); + super.dispose(); + } + + @override + void requestBeginFocus() { + if (focusNode.hasFocus == false && focusNode.canRequestFocus) { + FocusScope.of(context).requestFocus(focusNode); + } + } + + void _listenOnFocusNodeChanged() { + widget.onCellEditing.value = focusNode.hasFocus; + focusNode.setListener(() { + widget.onCellEditing.value = focusNode.hasFocus; + focusChanged(); + }); + } + + Future focusChanged() async {} +} + +class GridCellFocusListener extends ChangeNotifier { + VoidCallback? _listener; + + void setListener(VoidCallback listener) { if (_listener != null) { removeListener(_listener!); } _listener = listener; - super.addListener(listener); + addListener(listener); } void removeAllListener() { @@ -81,10 +186,10 @@ class GridCellRequestFocusNotifier extends ChangeNotifier { abstract class GridCellStyle {} -class CellSingleFocusNode extends FocusNode { +class SingleListenrFocusNode extends FocusNode { VoidCallback? _listener; - void setSingleListener(VoidCallback listener) { + void setListener(VoidCallback listener) { if (_listener != null) { removeListener(_listener!); } @@ -93,120 +198,9 @@ class CellSingleFocusNode extends FocusNode { super.addListener(listener); } - void removeSingleListener() { + void removeAllListener() { if (_listener != null) { removeListener(_listener!); } } } - -class CellStateNotifier extends ChangeNotifier { - bool _isFocus = false; - bool _onEnter = false; - - set isFocus(bool value) { - if (_isFocus != value) { - _isFocus = value; - notifyListeners(); - } - } - - set onEnter(bool value) { - if (_onEnter != value) { - _onEnter = value; - notifyListeners(); - } - } - - bool get isFocus => _isFocus; - - bool get onEnter => _onEnter; -} - -class CellContainer extends StatelessWidget { - final GridCellWidget child; - final Widget? expander; - final double width; - final RegionStateNotifier rowStateNotifier; - const CellContainer({ - Key? key, - required this.child, - required this.width, - required this.rowStateNotifier, - this.expander, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return ChangeNotifierProxyProvider( - create: (_) => CellStateNotifier(), - update: (_, row, cell) => cell!..onEnter = row.onEnter, - child: Selector( - selector: (context, notifier) => notifier.isFocus, - builder: (context, isFocus, _) { - Widget container = Center(child: child); - child.onFocus.addListener(() { - Provider.of(context, listen: false).isFocus = child.onFocus.value; - }); - - if (expander != null) { - container = CellEnterRegion(child: container, expander: expander!); - } - - return GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () => child.requestFocus.notify(), - child: Container( - constraints: BoxConstraints(maxWidth: width, minHeight: 46), - decoration: _makeBoxDecoration(context, isFocus), - padding: GridSize.cellContentInsets, - child: container, - ), - ); - }, - ), - ); - } - - BoxDecoration _makeBoxDecoration(BuildContext context, bool isFocus) { - final theme = context.watch(); - if (isFocus) { - final borderSide = BorderSide(color: theme.main1, width: 1.0); - return BoxDecoration(border: Border.fromBorderSide(borderSide)); - } else { - final borderSide = BorderSide(color: theme.shader5, width: 1.0); - return BoxDecoration(border: Border(right: borderSide, bottom: borderSide)); - } - } -} - -class CellEnterRegion extends StatelessWidget { - final Widget child; - final Widget expander; - const CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Selector( - selector: (context, notifier) => notifier.onEnter, - builder: (context, onEnter, _) { - List children = [child]; - if (onEnter) { - children.add(expander.positioned(right: 0)); - } - - return MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (p) => Provider.of(context, listen: false).onEnter = true, - onExit: (p) => Provider.of(context, listen: false).onEnter = false, - child: Stack( - alignment: AlignmentDirectional.center, - fit: StackFit.expand, - // alignment: AlignmentDirectional.centerEnd, - children: children, - ), - ); - }, - ); - } -} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_cotainer.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_cotainer.dart new file mode 100644 index 0000000000..fbb343dd6c --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_cotainer.dart @@ -0,0 +1,140 @@ +import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart'; +import 'package:flowy_infra/theme.dart'; +import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; +import 'package:styled_widget/styled_widget.dart'; + +import 'cell_accessory.dart'; +import 'cell_builder.dart'; +import 'cell_shortcuts.dart'; + +class CellContainer extends StatelessWidget { + final GridCellWidget child; + final AccessoryBuilder? accessoryBuilder; + final double width; + final RegionStateNotifier rowStateNotifier; + const CellContainer({ + Key? key, + required this.child, + required this.width, + required this.rowStateNotifier, + this.accessoryBuilder, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return ChangeNotifierProxyProvider( + create: (_) => CellContainerNotifier(child), + update: (_, rowStateNotifier, cellStateNotifier) => cellStateNotifier!..onEnter = rowStateNotifier.onEnter, + child: Selector( + selector: (context, notifier) => notifier.isFocus, + builder: (context, isFocus, _) { + Widget container = Center(child: GridCellShortcuts(child: child)); + + if (accessoryBuilder != null) { + final accessories = accessoryBuilder!(GridCellAccessoryBuildContext( + anchorContext: context, + isCellEditing: isFocus, + )); + + if (accessories.isNotEmpty) { + container = CellEnterRegion(child: container, accessories: accessories); + } + } + + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () => child.beginFocus.notify(), + child: Container( + constraints: BoxConstraints(maxWidth: width, minHeight: 46), + decoration: _makeBoxDecoration(context, isFocus), + padding: GridSize.cellContentInsets, + child: container, + ), + ); + }, + ), + ); + } + + BoxDecoration _makeBoxDecoration(BuildContext context, bool isFocus) { + final theme = context.watch(); + if (isFocus) { + final borderSide = BorderSide(color: theme.main1, width: 1.0); + return BoxDecoration(border: Border.fromBorderSide(borderSide)); + } else { + final borderSide = BorderSide(color: theme.shader5, width: 1.0); + return BoxDecoration(border: Border(right: borderSide, bottom: borderSide)); + } + } +} + +class CellEnterRegion extends StatelessWidget { + final Widget child; + final List accessories; + const CellEnterRegion({required this.child, required this.accessories, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Selector( + selector: (context, notifier) => notifier.onEnter, + builder: (context, onEnter, _) { + List children = [child]; + if (onEnter) { + children.add(CellAccessoryContainer(accessories: accessories).positioned(right: 0)); + } + + return MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (p) => Provider.of(context, listen: false).onEnter = true, + onExit: (p) => Provider.of(context, listen: false).onEnter = false, + child: Stack( + alignment: AlignmentDirectional.center, + fit: StackFit.expand, + children: children, + ), + ); + }, + ); + } +} + +class CellContainerNotifier extends ChangeNotifier { + final CellEditable cellEditable; + bool mouted = false; + VoidCallback? _onCellFocusListener; + bool _isFocus = false; + bool _onEnter = false; + + CellContainerNotifier(this.cellEditable) { + _onCellFocusListener = () => isFocus = cellEditable.onCellFocus.value; + cellEditable.onCellFocus.addListener(_onCellFocusListener!); + } + + @override + void dispose() { + if (_onCellFocusListener != null) { + cellEditable.onCellFocus.removeListener(_onCellFocusListener!); + } + super.dispose(); + } + + set isFocus(bool value) { + if (_isFocus != value) { + _isFocus = value; + notifyListeners(); + } + } + + set onEnter(bool value) { + if (_onEnter != value) { + _onEnter = value; + notifyListeners(); + } + } + + bool get isFocus => _isFocus; + + bool get onEnter => _onEnter; +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_shortcuts.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_shortcuts.dart new file mode 100644 index 0000000000..f4f222f219 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_shortcuts.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +typedef CellKeyboardAction = dynamic Function(); + +enum CellKeyboardKey { + onEnter, + onCopy, + onInsert, +} + +abstract class CellShortcuts extends Widget { + const CellShortcuts({Key? key}) : super(key: key); + + Map get shortcutHandlers; +} + +class GridCellShortcuts extends StatelessWidget { + final CellShortcuts child; + const GridCellShortcuts({required this.child, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Shortcuts( + shortcuts: { + LogicalKeySet(LogicalKeyboardKey.enter): const GridCellEnterIdent(), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyC): const GridCellCopyIntent(), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyV): const GridCellInsertIntent(), + }, + child: Actions( + actions: { + GridCellEnterIdent: GridCellEnterAction(child: child), + GridCellCopyIntent: GridCellCopyAction(child: child), + GridCellInsertIntent: GridCellInsertAction(child: child), + }, + child: child, + ), + ); + } +} + +class GridCellEnterIdent extends Intent { + const GridCellEnterIdent(); +} + +class GridCellEnterAction extends Action { + final CellShortcuts child; + GridCellEnterAction({required this.child}); + + @override + void invoke(covariant GridCellEnterIdent intent) { + final callback = child.shortcutHandlers[CellKeyboardKey.onEnter]; + if (callback != null) { + callback(); + } + } +} + +class GridCellCopyIntent extends Intent { + const GridCellCopyIntent(); +} + +class GridCellCopyAction extends Action { + final CellShortcuts child; + GridCellCopyAction({required this.child}); + + @override + void invoke(covariant GridCellCopyIntent intent) { + final callback = child.shortcutHandlers[CellKeyboardKey.onCopy]; + if (callback == null) { + return; + } + + final s = callback(); + if (s is String) { + Clipboard.setData(ClipboardData(text: s)); + } + } +} + +class GridCellInsertIntent extends Intent { + const GridCellInsertIntent(); +} + +class GridCellInsertAction extends Action { + final CellShortcuts child; + GridCellInsertAction({required this.child}); + + @override + void invoke(covariant GridCellInsertIntent intent) { + final callback = child.shortcutHandlers[CellKeyboardKey.onInsert]; + if (callback != null) { + callback(); + } + } +} 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 b2493d55ed..384d85737f 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 StatefulWidget with GridCellWidget { +class CheckboxCell extends GridCellWidget { final GridCellContextBuilder cellContextBuilder; CheckboxCell({ required this.cellContextBuilder, @@ -14,17 +14,16 @@ class CheckboxCell extends StatefulWidget with GridCellWidget { }) : super(key: key); @override - State createState() => _CheckboxCellState(); + GridCellState createState() => _CheckboxCellState(); } -class _CheckboxCellState extends State { +class _CheckboxCellState extends GridCellState { late CheckboxCellBloc _cellBloc; @override void initState() { final cellContext = widget.cellContextBuilder.build(); _cellBloc = getIt(param1: cellContext)..add(const CheckboxCellEvent.initial()); - _listenCellRequestFocus(); super.initState(); } @@ -49,22 +48,23 @@ class _CheckboxCellState extends State { ); } - @override - void didUpdateWidget(covariant CheckboxCell oldWidget) { - _listenCellRequestFocus(); - super.didUpdateWidget(oldWidget); - } - @override Future dispose() async { - widget.requestFocus.removeAllListener(); _cellBloc.close(); super.dispose(); } - void _listenCellRequestFocus() { - widget.requestFocus.addListener(() { - _cellBloc.add(const CheckboxCellEvent.select()); - }); + @override + void requestBeginFocus() { + _cellBloc.add(const CheckboxCellEvent.select()); + } + + @override + String? onCopy() { + if (_cellBloc.state.isSelected) { + return "Yes"; + } else { + return "No"; + } } } 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 417c9f270e..3e7d40c796 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 StatefulWidget with GridCellWidget { +class DateCell extends GridCellWidget { final GridCellContextBuilder cellContextBuilder; late final DateCellStyle? cellStyle; @@ -35,10 +35,10 @@ class DateCell extends StatefulWidget with GridCellWidget { } @override - State createState() => _DateCellState(); + GridCellState createState() => _DateCellState(); } -class _DateCellState extends State { +class _DateCellState extends GridCellState { late DateCellBloc _cellBloc; @override @@ -64,7 +64,7 @@ class _DateCellState extends State { cursor: SystemMouseCursors.click, child: Align( alignment: alignment, - child: FlowyText.medium(state.data.foldRight("", (data, _) => data.date), fontSize: 12), + child: FlowyText.medium(state.dateStr, fontSize: 12), ), ), ), @@ -76,8 +76,8 @@ class _DateCellState extends State { void _showCalendar(BuildContext context) { final bloc = context.read(); - widget.onFocus.value = true; - final calendar = DateCellEditor(onDismissed: () => widget.onFocus.value = false); + widget.onCellEditing.value = true; + final calendar = DateCellEditor(onDismissed: () => widget.onCellEditing.value = false); calendar.show( context, cellContext: bloc.cellContext.clone(), @@ -89,4 +89,10 @@ class _DateCellState extends State { _cellBloc.close(); super.dispose(); } + + @override + void requestBeginFocus() {} + + @override + String? onCopy() => _cellBloc.state.dateStr; } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart index 016eb00e85..93d304cd1a 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart @@ -160,18 +160,21 @@ class _CellCalendarWidget extends StatelessWidget { ), ), selectedDayPredicate: (day) { - return state.dateData.fold( + return state.calData.fold( () => false, (dateData) => isSameDay(dateData.date, day), ); }, onDaySelected: (selectedDay, focusedDay) { + _CalDateTimeSetting.hide(context); context.read().add(DateCalEvent.selectDay(selectedDay)); }, onFormatChanged: (format) { + _CalDateTimeSetting.hide(context); context.read().add(DateCalEvent.setCalFormat(format)); }, onPageChanged: (focusedDay) { + _CalDateTimeSetting.hide(context); context.read().add(DateCalEvent.setFocusedDay(focusedDay)); }, ); @@ -234,6 +237,7 @@ class _TimeTextFieldState extends State<_TimeTextField> { if (widget.bloc.state.dateTypeOption.includeTime) { _focusNode.addListener(() { if (mounted) { + _CalDateTimeSetting.hide(context); widget.bloc.add(DateCalEvent.setTime(_controller.text)); } }); @@ -257,6 +261,7 @@ class _TimeTextFieldState extends State<_TimeTextField> { child: RoundedInputField( height: 40, focusNode: _focusNode, + hintText: state.timeHintText, controller: _controller, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), normalBorderColor: theme.shader4, @@ -326,6 +331,7 @@ class _CalDateTimeSetting extends StatefulWidget { } void show(BuildContext context) { + hide(context); FlowyOverlay.of(context).insertWithAnchor( widget: OverlayContainer( child: this, @@ -337,6 +343,10 @@ class _CalDateTimeSetting extends StatefulWidget { anchorOffset: const Offset(20, 0), ); } + + static void hide(BuildContext context) { + FlowyOverlay.of(context).remove(identifier()); + } } class _CalDateTimeSettingState extends State<_CalDateTimeSetting> { 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 dc694f48f9..7d16b16ef0 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 @@ -1,5 +1,4 @@ import 'dart:async'; - import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/grid/prelude.dart'; import 'package:flutter/material.dart'; @@ -7,7 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'cell_builder.dart'; -class NumberCell extends StatefulWidget with GridCellWidget { +class NumberCell extends GridCellWidget { final GridCellContextBuilder cellContextBuilder; NumberCell({ @@ -16,101 +15,79 @@ class NumberCell extends StatefulWidget with GridCellWidget { }) : super(key: key); @override - State createState() => _NumberCellState(); + GridFocusNodeCellState createState() => _NumberCellState(); } -class _NumberCellState extends State { +class _NumberCellState extends GridFocusNodeCellState { late NumberCellBloc _cellBloc; late TextEditingController _controller; - late CellSingleFocusNode _focusNode; Timer? _delayOperation; @override void initState() { final cellContext = widget.cellContextBuilder.build(); _cellBloc = getIt(param1: cellContext)..add(const NumberCellEvent.initial()); - _controller = TextEditingController(text: _cellBloc.state.content); - _focusNode = CellSingleFocusNode(); - _listenFocusNode(); + _controller = TextEditingController(text: contentFromState(_cellBloc.state)); super.initState(); } @override Widget build(BuildContext context) { - _listenCellRequestFocus(context); return BlocProvider.value( value: _cellBloc, - child: BlocConsumer( - listener: (context, state) { - if (_controller.text != state.content) { - _controller.text = state.content; - } - }, - builder: (context, state) { - return TextField( - controller: _controller, - focusNode: _focusNode, - onEditingComplete: () => _focusNode.unfocus(), - maxLines: null, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - decoration: const InputDecoration( - contentPadding: EdgeInsets.zero, - border: InputBorder.none, - isDense: true, - ), - ); - }, + child: MultiBlocListener( + listeners: [ + BlocListener( + listenWhen: (p, c) => p.content != c.content, + listener: (context, state) => _controller.text = contentFromState(state), + ), + ], + child: TextField( + controller: _controller, + focusNode: focusNode, + onEditingComplete: () => focusNode.unfocus(), + maxLines: null, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + decoration: const InputDecoration( + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + isDense: true, + ), + ), ), ); } @override Future dispose() async { - widget.requestFocus.removeAllListener(); _delayOperation?.cancel(); _cellBloc.close(); - _focusNode.removeSingleListener(); - _focusNode.dispose(); super.dispose(); } @override - void didUpdateWidget(covariant NumberCell oldWidget) { - if (oldWidget != widget) { - _listenFocusNode(); - } - super.didUpdateWidget(oldWidget); - } - Future focusChanged() async { if (mounted) { _delayOperation?.cancel(); _delayOperation = Timer(const Duration(milliseconds: 300), () { - if (_cellBloc.isClosed == false && _controller.text != _cellBloc.state.content) { - final number = num.tryParse(_controller.text); - if (number != null) { - _cellBloc.add(NumberCellEvent.updateCell(_controller.text)); - } else { - _controller.text = ""; - } + if (_cellBloc.isClosed == false && _controller.text != contentFromState(_cellBloc.state)) { + _cellBloc.add(NumberCellEvent.updateCell(_controller.text)); } }); } } - void _listenFocusNode() { - widget.onFocus.value = _focusNode.hasFocus; - _focusNode.setSingleListener(() { - widget.onFocus.value = _focusNode.hasFocus; - focusChanged(); - }); + String contentFromState(NumberCellState state) { + return state.content.fold((l) => l, (r) => ""); } - void _listenCellRequestFocus(BuildContext context) { - widget.requestFocus.addListener(() { - if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) { - FocusScope.of(context).requestFocus(_focusNode); - } - }); + @override + String? onCopy() { + return _cellBloc.state.content.fold((content) => content, (r) => null); + } + + @override + void onInsert(String value) { + _cellBloc.add(NumberCellEvent.updateCell(value)); } } 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 6f8212106a..27e36ad46d 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 @@ -64,9 +64,11 @@ class SelectOptionTag extends StatelessWidget { final String name; final Color color; final bool isSelected; + final VoidCallback? onSelected; const SelectOptionTag({ required this.name, required this.color, + this.onSelected, this.isSelected = false, Key? key, }) : super(key: key); @@ -74,12 +76,14 @@ class SelectOptionTag extends StatelessWidget { factory SelectOptionTag.fromSelectOption({ required BuildContext context, required SelectOption option, + VoidCallback? onSelected, bool isSelected = false, }) { return SelectOptionTag( name: option.name, color: option.color.make(context), isSelected: isSelected, + onSelected: onSelected, ); } @@ -92,19 +96,12 @@ class SelectOptionTag extends StatelessWidget { backgroundColor: color, labelPadding: const EdgeInsets.symmetric(horizontal: 6), selected: true, - onSelected: (_) {}, + onSelected: (_) { + if (onSelected != null) { + onSelected!(); + } + }, ); - - // return Container( - // decoration: BoxDecoration( - // color: option.color.make(context), - // shape: BoxShape.rectangle, - // borderRadius: BorderRadius.circular(8.0), - // ), - // child: Center(child: FlowyText.medium(option.name, fontSize: 12)), - // margin: const EdgeInsets.symmetric(horizontal: 3.0), - // padding: const EdgeInsets.symmetric(horizontal: 6.0), - // ); } } @@ -136,7 +133,11 @@ class SelectOptionTagCell extends StatelessWidget { Flexible( fit: FlexFit.loose, flex: 2, - child: SelectOptionTag.fromSelectOption(context: context, option: option), + child: SelectOptionTag.fromSelectOption( + context: context, + option: option, + onSelected: () => onSelected(option), + ), ), const Spacer(), ...children, 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 c57a1865be..e878ac2c58 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 StatefulWidget with GridCellWidget { +class SingleSelectCell extends GridCellWidget { final GridCellContextBuilder cellContextBuilder; late final SelectOptionCellStyle? cellStyle; @@ -59,7 +59,7 @@ class _SingleSelectCellState extends State { return _SelectOptionCell( selectOptions: state.selectedOptions, cellStyle: widget.cellStyle, - onFocus: (value) => widget.onFocus.value = value, + onFocus: (value) => widget.onCellEditing.value = value, cellContextBuilder: widget.cellContextBuilder); }, ), @@ -74,7 +74,7 @@ class _SingleSelectCellState extends State { } //---------------------------------------------------------------- -class MultiSelectCell extends StatefulWidget with GridCellWidget { +class MultiSelectCell extends GridCellWidget { final GridCellContextBuilder cellContextBuilder; late final SelectOptionCellStyle? cellStyle; @@ -113,7 +113,7 @@ class _MultiSelectCellState extends State { return _SelectOptionCell( selectOptions: state.selectedOptions, cellStyle: widget.cellStyle, - onFocus: (value) => widget.onFocus.value = value, + onFocus: (value) => widget.onCellEditing.value = value, cellContextBuilder: widget.cellContextBuilder); }, ), 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 01972eb41a..51c550ccb2 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 @@ -156,6 +156,7 @@ class _TextField extends StatelessWidget { selectedOptionMap: optionMap, distanceToText: _editorPannelWidth * 0.7, tagController: _tagController, + onClick: () => FlowyOverlay.of(context).remove(SelectOptionTypeOptionEditor.identifier), newText: (text) { context.read().add(SelectOptionEditorEvent.filterOption(text)); }, @@ -207,6 +208,7 @@ class _CreateOptionCell extends StatelessWidget { SelectOptionTag( name: name, color: theme.shader6, + onSelected: () => context.read().add(SelectOptionEditorEvent.newOption(name)), ), ], ); @@ -233,7 +235,11 @@ class _SelectOptionCell extends StatelessWidget { context.read().add(SelectOptionEditorEvent.selectOption(option.id)); }, children: [ - if (isSelected) svgWidget("grid/checkmark"), + if (isSelected) + Padding( + padding: const EdgeInsets.only(right: 6), + child: svgWidget("grid/checkmark"), + ), ], ), ), diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart index 125a86a609..398d98a994 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart @@ -22,6 +22,7 @@ class SelectOptionTextField extends StatelessWidget { final Function(String) onNewTag; final Function(String) newText; + final VoidCallback? onClick; SelectOptionTextField({ required this.options, @@ -30,6 +31,7 @@ class SelectOptionTextField extends StatelessWidget { required this.tagController, required this.onNewTag, required this.newText, + this.onClick, TextEditingController? controller, FocusNode? focusNode, Key? key, @@ -53,6 +55,7 @@ class SelectOptionTextField extends StatelessWidget { autofocus: true, controller: editController, focusNode: focusNode, + onTap: onClick, onChanged: (text) { if (onChanged != null) { onChanged(text); 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 1563d41d86..1bece5a3d7 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 StatefulWidget with GridCellWidget { +class GridTextCell extends GridCellWidget { final GridCellContextBuilder cellContextBuilder; late final GridTextCellStyle? cellStyle; GridTextCell({ @@ -29,13 +29,12 @@ class GridTextCell extends StatefulWidget with GridCellWidget { } @override - State createState() => _GridTextCellState(); + GridFocusNodeCellState createState() => _GridTextCellState(); } -class _GridTextCellState extends State { +class _GridTextCellState extends GridFocusNodeCellState { late TextCellBloc _cellBloc; late TextEditingController _controller; - late CellSingleFocusNode _focusNode; Timer? _delayOperation; @override @@ -44,10 +43,6 @@ class _GridTextCellState extends State { _cellBloc = getIt(param1: cellContext); _cellBloc.add(const TextCellEvent.initial()); _controller = TextEditingController(text: _cellBloc.state.content); - _focusNode = CellSingleFocusNode(); - - _listenFocusNode(); - _listenRequestFocus(context); super.initState(); } @@ -63,9 +58,9 @@ class _GridTextCellState extends State { }, child: TextField( controller: _controller, - focusNode: _focusNode, + focusNode: focusNode, onChanged: (value) => focusChanged(), - onEditingComplete: () => _focusNode.unfocus(), + onEditingComplete: () => focusNode.unfocus(), maxLines: null, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), decoration: InputDecoration( @@ -81,39 +76,12 @@ class _GridTextCellState extends State { @override Future dispose() async { - widget.requestFocus.removeAllListener(); _delayOperation?.cancel(); _cellBloc.close(); - _focusNode.removeSingleListener(); - _focusNode.dispose(); - super.dispose(); } @override - void didUpdateWidget(covariant GridTextCell oldWidget) { - if (oldWidget != widget) { - _listenFocusNode(); - } - super.didUpdateWidget(oldWidget); - } - - void _listenFocusNode() { - widget.onFocus.value = _focusNode.hasFocus; - _focusNode.setSingleListener(() { - widget.onFocus.value = _focusNode.hasFocus; - focusChanged(); - }); - } - - void _listenRequestFocus(BuildContext context) { - widget.requestFocus.addListener(() { - if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) { - FocusScope.of(context).requestFocus(_focusNode); - } - }); - } - Future focusChanged() async { if (mounted) { _delayOperation?.cancel(); @@ -124,4 +92,12 @@ class _GridTextCellState extends State { }); } } + + @override + String? onCopy() => _cellBloc.state.content; + + @override + void onInsert(String value) { + _cellBloc.add(TextCellEvent.updateText(value)); + } } 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 index 055a4947c8..f4da18be86 100644 --- 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 @@ -6,9 +6,10 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; -class URLCellEditor extends StatefulWidget { +class URLCellEditor extends StatefulWidget with FlowyOverlayDelegate { final GridURLCellContext cellContext; - const URLCellEditor({required this.cellContext, Key? key}) : super(key: key); + final VoidCallback completed; + const URLCellEditor({required this.cellContext, required this.completed, Key? key}) : super(key: key); @override State createState() => _URLCellEditorState(); @@ -16,27 +17,43 @@ class URLCellEditor extends StatefulWidget { static void show( BuildContext context, GridURLCellContext cellContext, + VoidCallback completed, ) { FlowyOverlay.of(context).remove(identifier()); final editor = URLCellEditor( cellContext: cellContext, + completed: completed, ); // FlowyOverlay.of(context).insertWithAnchor( widget: OverlayContainer( - child: SizedBox(width: 200, child: editor), + child: SizedBox( + width: 200, + child: Padding(padding: const EdgeInsets.all(6), child: editor), + ), constraints: BoxConstraints.loose(const Size(300, 160)), ), identifier: URLCellEditor.identifier(), anchorContext: context, anchorDirection: AnchorDirection.bottomWithCenterAligned, + delegate: editor, ); } static String identifier() { return (URLCellEditor).toString(); } + + @override + bool asBarrier() { + return true; + } + + @override + void didRemove() { + completed(); + } } class _URLCellEditorState extends State { 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 index db0dcade79..e37dca6632 100644 --- 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 @@ -1,10 +1,13 @@ import 'dart:async'; +import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/workspace/application/grid/cell/url_cell_bloc.dart'; +import 'package:app_flowy/workspace/presentation/home/toast.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart'; +import 'package:easy_localization/easy_localization.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/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:app_flowy/workspace/application/grid/prelude.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -14,12 +17,20 @@ import 'cell_editor.dart'; class GridURLCellStyle extends GridCellStyle { String? placeholder; + List accessoryTypes; + GridURLCellStyle({ this.placeholder, + this.accessoryTypes = const [], }); } -class GridURLCell extends StatefulWidget with GridCellWidget { +enum GridURLCellAccessoryType { + edit, + copyURL, +} + +class GridURLCell extends GridCellWidget { final GridCellContextBuilder cellContextBuilder; late final GridURLCellStyle? cellStyle; GridURLCell({ @@ -35,10 +46,39 @@ class GridURLCell extends StatefulWidget with GridCellWidget { } @override - State createState() => _GridURLCellState(); + GridCellState createState() => _GridURLCellState(); + + GridCellAccessory accessoryFromType(GridURLCellAccessoryType ty, GridCellAccessoryBuildContext buildContext) { + switch (ty) { + case GridURLCellAccessoryType.edit: + final cellContext = cellContextBuilder.build() as GridURLCellContext; + return _EditURLAccessory(cellContext: cellContext, anchorContext: buildContext.anchorContext); + + case GridURLCellAccessoryType.copyURL: + final cellContext = cellContextBuilder.build() as GridURLCellContext; + return _CopyURLAccessory(cellContext: cellContext); + } + } + + @override + List Function(GridCellAccessoryBuildContext buildContext) get accessoryBuilder => (buildContext) { + final List accessories = []; + if (cellStyle != null) { + accessories.addAll(cellStyle!.accessoryTypes.map((ty) { + return accessoryFromType(ty, buildContext); + })); + } + + // If the accessories is empty then the default accessory will be GridURLCellAccessoryType.edit + if (accessories.isEmpty) { + accessories.add(accessoryFromType(GridURLCellAccessoryType.edit, buildContext)); + } + + return accessories; + }; } -class _GridURLCellState extends State { +class _GridURLCellState extends GridCellState { late URLCellBloc _cellBloc; @override @@ -46,7 +86,6 @@ class _GridURLCellState extends State { final cellContext = widget.cellContextBuilder.build() as GridURLCellContext; _cellBloc = URLCellBloc(cellContext: cellContext); _cellBloc.add(const URLCellEvent.initial()); - _listenRequestFocus(context); super.initState(); } @@ -66,14 +105,17 @@ class _GridURLCellState extends State { fontSize: 14, decoration: TextDecoration.underline, ), - recognizer: _tapGesture(context), ), ); - return CellEnterRegion( + return SizedBox.expand( + child: GestureDetector( child: Align(alignment: Alignment.centerLeft, child: richText), - expander: _EditCellIndicator(onTap: () {}), - ); + onTap: () async { + final url = context.read().state.url; + await _openUrlOrEdit(url); + }, + )); }, ), ); @@ -81,51 +123,72 @@ class _GridURLCellState extends State { @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); + widget.onCellEditing.value = true; + URLCellEditor.show(context, cellContext, () { + widget.onCellEditing.value = false; + }); } } - void _listenRequestFocus(BuildContext context) { - widget.requestFocus.addListener(() { - _openUrlOrEdit(_cellBloc.state.url); - }); + @override + void requestBeginFocus() { + _openUrlOrEdit(_cellBloc.state.url); + } + + @override + String? onCopy() => _cellBloc.state.content; + + @override + void onInsert(String value) { + _cellBloc.add(URLCellEvent.updateURL(value)); } } -class _EditCellIndicator extends StatelessWidget { - final VoidCallback onTap; - const _EditCellIndicator({required this.onTap, Key? key}) : super(key: key); +class _EditURLAccessory extends StatelessWidget with GridCellAccessory { + final GridURLCellContext cellContext; + final BuildContext anchorContext; + const _EditURLAccessory({ + required this.cellContext, + required this.anchorContext, + 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), - ); + return svgWidget("editor/edit", color: theme.iconColor); + } + + @override + void onTap() { + URLCellEditor.show(anchorContext, cellContext, () {}); + } +} + +class _CopyURLAccessory extends StatelessWidget with GridCellAccessory { + final GridURLCellContext cellContext; + const _CopyURLAccessory({required this.cellContext, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return svgWidget("editor/copy", color: theme.iconColor); + } + + @override + void onTap() { + final content = cellContext.getCellData(loadIfNoCache: false)?.content ?? ""; + Clipboard.setData(ClipboardData(text: content)); + showMessageToast(LocaleKeys.grid_row_copyProperty.tr()); } } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart index 48866457f7..5071161d5a 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart @@ -6,7 +6,6 @@ import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -24,6 +23,7 @@ class GridFieldCell extends StatelessWidget { return BlocProvider( create: (context) => FieldCellBloc(cellContext: cellContext)..add(const FieldCellEvent.initial()), child: BlocBuilder( + // buildWhen: (p, c) => p.field != c.field, builder: (context, state) { final button = FieldCellButton( field: state.field, @@ -38,7 +38,7 @@ class GridFieldCell extends StatelessWidget { ); return _GridHeaderCellContainer( - width: state.field.width.toDouble(), + width: state.width, child: Stack( alignment: Alignment.centerRight, fit: StackFit.expand, @@ -60,13 +60,14 @@ class GridFieldCell extends StatelessWidget { void _showFieldEditor(BuildContext context) { final state = context.read().state; + final field = state.field; FieldEditor( gridId: state.gridId, - fieldName: state.field.name, + fieldName: field.name, contextLoader: FieldContextLoader( gridId: state.gridId, - field: state.field, + field: field, ), ).show(context); } @@ -84,7 +85,7 @@ class _GridHeaderCellContainer extends StatelessWidget { @override Widget build(BuildContext context) { final theme = context.watch(); - final borderSide = BorderSide(color: theme.shader4, width: 0.4); + final borderSide = BorderSide(color: theme.shader5, width: 1.0); final decoration = BoxDecoration( border: Border( top: borderSide, @@ -113,21 +114,19 @@ class _DragToExpandLine extends StatelessWidget { onTap: () {}, child: GestureDetector( behavior: HitTestBehavior.opaque, - onHorizontalDragCancel: () {}, onHorizontalDragUpdate: (value) { - // context.read().add(FieldCellEvent.updateWidth(value.delta.dx)); - Log.info(value); + context.read().add(FieldCellEvent.startUpdateWidth(value.delta.dx)); }, onHorizontalDragEnd: (end) { - Log.info(end); + context.read().add(const FieldCellEvent.endUpdateWidth()); }, child: FlowyHover( style: HoverStyle( hoverColor: theme.main1, borderRadius: BorderRadius.zero, - contentMargin: const EdgeInsets.only(left: 5), + contentMargin: const EdgeInsets.only(left: 6), ), - child: const SizedBox(width: 2), + child: const SizedBox(width: 4), ), ), ); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart index 3a330a02b7..bca17e1cd8 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart @@ -9,7 +9,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/format.pbenum.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart' hide NumberFormat; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart index dada59b41c..cec32bd99e 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart @@ -25,6 +25,8 @@ class SelectOptionTypeOptionEditor extends StatelessWidget { Key? key, }) : super(key: key); + static String get identifier => (SelectOptionTypeOptionEditor).toString(); + @override Widget build(BuildContext context) { return BlocProvider( 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 a643b58928..15dbb0bfc7 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 @@ -1,5 +1,7 @@ import 'package:app_flowy/workspace/application/grid/prelude.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_cotainer.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; @@ -170,16 +172,29 @@ class _RowCells extends StatelessWidget { List _makeCells(BuildContext context, GridCellMap gridCellMap) { return gridCellMap.values.map( (gridCell) { - Widget? expander; - if (gridCell.field.isPrimary) { - expander = _CellExpander(onExpand: onExpand); + final GridCellWidget child = buildGridCellWidget(gridCell, cellCache); + + accessoryBuilder(GridCellAccessoryBuildContext buildContext) { + final builder = child.accessoryBuilder; + List accessories = []; + if (gridCell.field.isPrimary) { + accessories.add(PrimaryCellAccessory( + onTapCallback: onExpand, + isCellEditing: buildContext.isCellEditing, + )); + } + + if (builder != null) { + accessories.addAll(builder(buildContext)); + } + return accessories; } return CellContainer( width: gridCell.field.width.toDouble(), - child: buildGridCellWidget(gridCell, cellCache), + child: child, rowStateNotifier: Provider.of(context, listen: false), - expander: expander, + accessoryBuilder: accessoryBuilder, ); }, ).toList(); @@ -199,26 +214,6 @@ class RegionStateNotifier extends ChangeNotifier { bool get onEnter => _onEnter; } -class _CellExpander extends StatelessWidget { - final VoidCallback onExpand; - const _CellExpander({required this.onExpand, Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final theme = context.watch(); - return FittedBox( - fit: BoxFit.contain, - child: FlowyIconButton( - width: 26, - onPressed: onExpand, - iconPadding: const EdgeInsets.all(5), - radius: BorderRadius.circular(4), - icon: svgWidget("grid/expander", color: theme.main1), - ), - ); - } -} - class _RowEnterRegion extends StatefulWidget { final Widget child; const _RowEnterRegion({required this.child, Key? key}) : super(key: key); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/number_cell.dart deleted file mode 100644 index 0f3f7c5f32..0000000000 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/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/row_action_sheet.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_action_sheet.dart index 40e77d9c43..1d7886c86c 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_action_sheet.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_action_sheet.dart @@ -24,6 +24,7 @@ class GridRowActionSheet extends StatelessWidget { child: BlocBuilder( builder: (context, state) { final cells = _RowAction.values + .where((value) => value.enable()) .map( (action) => _RowActionCell( action: action, 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 0900039b1f..719e0082fb 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 @@ -3,6 +3,7 @@ import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; 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/cell_accessory.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'; @@ -10,7 +11,6 @@ import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; @@ -149,12 +149,18 @@ class _RowDetailCell extends StatelessWidget { @override Widget build(BuildContext context) { final theme = context.watch(); + final style = _customCellStyle(theme, gridCell.field.fieldType); + final cell = buildGridCellWidget(gridCell, cellCache, style: style); - final cell = buildGridCellWidget( - gridCell, - cellCache, - style: _buildCellStyle(theme, gridCell.field.fieldType), + final gesture = GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () => cell.beginFocus.notify(), + child: AccessoryHover( + child: cell, + contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12), + ), ); + return ConstrainedBox( constraints: const BoxConstraints(minHeight: 40), child: IntrinsicHeight( @@ -167,12 +173,7 @@ class _RowDetailCell extends StatelessWidget { child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)), ), const HSpace(10), - Expanded( - child: FlowyHover2( - child: cell, - contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12), - ), - ), + Expanded(child: gesture), ], ), ), @@ -191,7 +192,7 @@ class _RowDetailCell extends StatelessWidget { } } -GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) { +GridCellStyle? _customCellStyle(AppTheme theme, FieldType fieldType) { switch (fieldType) { case FieldType.Checkbox: return null; @@ -217,7 +218,11 @@ GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) { case FieldType.URL: return GridURLCellStyle( placeholder: LocaleKeys.grid_row_textPlaceholder.tr(), + accessoryTypes: [ + GridURLCellAccessoryType.edit, + GridURLCellAccessoryType.copyURL, + ], ); } - return null; + throw UnimplementedError; } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/shortcuts.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/shortcuts.dart new file mode 100644 index 0000000000..1e38c5647d --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/shortcuts.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class GridShortcuts extends StatelessWidget { + final Widget child; + const GridShortcuts({required this.child, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Shortcuts( + shortcuts: bindKeys([]), + child: Actions( + dispatcher: LoggingActionDispatcher(), + actions: const {}, + child: child, + ), + ); + } +} + +Map bindKeys(List keys) { + return {for (var key in keys) LogicalKeySet(key): KeyboardKeyIdent(key)}; +} + +Map> bindActions() { + return { + KeyboardKeyIdent: KeyboardBindingAction(), + }; +} + +class KeyboardKeyIdent extends Intent { + final KeyboardKey key; + + const KeyboardKeyIdent(this.key); +} + +class KeyboardBindingAction extends Action { + KeyboardBindingAction(); + + @override + void invoke(covariant KeyboardKeyIdent intent) { + // print(intent); + } +} + +class LoggingActionDispatcher extends ActionDispatcher { + @override + Object? invokeAction( + covariant Action action, + covariant Intent intent, [ + BuildContext? context, + ]) { + // print('Action invoked: $action($intent) from $context'); + super.invokeAction(action, intent, context); + + return null; + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_setting.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_setting.dart index 05a72a5504..7e3c14e021 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_setting.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_setting.dart @@ -85,7 +85,7 @@ class GridSettingList extends StatelessWidget { } Widget _renderList() { - final cells = GridSettingAction.values.map((action) { + final cells = GridSettingAction.values.where((value) => value.enable()).map((action) { return _SettingItem(action: action); }).toList(); diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_button.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_button.dart index eeafd4ae98..de8434a1dd 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_button.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_button.dart @@ -27,7 +27,7 @@ class _EmojiStyleButtonState extends State { bool _isToggled = false; // Style get _selectionStyle => widget.controller.getSelectionStyle(); final GlobalKey emojiButtonKey = GlobalKey(); - OverlayEntry _entry = OverlayEntry(builder: (context) => Container()); + OverlayEntry? _entry; // final FocusNode _keyFocusNode = FocusNode(); @override @@ -52,6 +52,12 @@ class _EmojiStyleButtonState extends State { ); } + @override + void dispose() { + _entry?.remove(); + super.dispose(); + } + // @override // void didUpdateWidget(covariant FlowyEmojiStyleButton oldWidget) { // super.didUpdateWidget(oldWidget); @@ -77,8 +83,9 @@ class _EmojiStyleButtonState extends State { // } void _toggleAttribute() { - if (_entry.mounted) { - _entry.remove(); + if (_entry?.mounted ?? false) { + _entry?.remove(); + _entry = null; setState(() => _isToggled = false); } else { RenderBox box = emojiButtonKey.currentContext?.findRenderObject() as RenderBox; @@ -93,7 +100,7 @@ class _EmojiStyleButtonState extends State { ), ); - Overlay.of(context)!.insert(_entry); + Overlay.of(context)!.insert(_entry!); setState(() => _isToggled = 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 762a978693..6cc150489c 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 @@ -1,5 +1,5 @@ import 'package:app_flowy/startup/tasks/rust_sdk.dart'; -import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; +import 'package:app_flowy/workspace/presentation/home/toast.dart'; import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme.dart'; @@ -16,7 +16,6 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:device_info_plus/device_info_plus.dart'; -import 'package:fluttertoast/fluttertoast.dart'; class QuestionBubble extends StatelessWidget { const QuestionBubble({Key? key}) : super(key: key); @@ -46,7 +45,7 @@ class QuestionBubble extends StatelessWidget { _launchURL("https://discord.gg/9Q2xaN37tV"); break; case BubbleAction.debug: - const _DebugToast().show(); + _DebugToast().show(); break; } }); @@ -71,55 +70,14 @@ class QuestionBubble extends StatelessWidget { } } -class _DebugToast extends StatelessWidget { - const _DebugToast({Key? key}) : super(key: key); +class _DebugToast { + void show() async { + var debugInfo = ""; + debugInfo += await _getDeviceInfo(); + debugInfo += await _getDocumentPath(); + Clipboard.setData(ClipboardData(text: debugInfo)); - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: Future(() async { - var debugInfo = ""; - debugInfo += await _getDeviceInfo(); - debugInfo += await _getDocumentPath(); - - Clipboard.setData(ClipboardData(text: debugInfo)); - }), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.hasError) { - return _done(context, Text("Error: ${snapshot.error}")); - } else { - return _done(context, null); - } - } else { - return const CircularProgressIndicator(); - } - }, - ); - } - - Widget _done(BuildContext context, Widget? error) { - final theme = context.watch(); - return Container( - padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0), - decoration: BoxDecoration(borderRadius: BorderRadius.circular(25.0), color: theme.main1), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.check), - const SizedBox(width: 12.0), - (error == null) ? Text(LocaleKeys.questionBubble_debug_success.tr()) : error - ], - ), - ); - } - - void show() { - fToast.showToast( - child: this, - gravity: ToastGravity.BOTTOM, - toastDuration: const Duration(seconds: 3), - ); + showMessageToast(LocaleKeys.questionBubble_debug_success.tr()); } Future _getDeviceInfo() async { diff --git a/frontend/app_flowy/macos/Podfile b/frontend/app_flowy/macos/Podfile index dade8dfad0..e806f574bd 100644 --- a/frontend/app_flowy/macos/Podfile +++ b/frontend/app_flowy/macos/Podfile @@ -26,6 +26,23 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_macos_podfile_setup +def build_specify_archs_only + if ENV.has_key?('BUILD_ARCHS') + xcodeproj_path = File.dirname(__FILE__) + '/Runner.xcodeproj' + project = Xcodeproj::Project.open(xcodeproj_path) + project.targets.each do |target| + if target.name == 'Runner' + target.build_configurations.each do |config| + config.build_settings['ARCHS'] = ENV['BUILD_ARCHS'] + end + end + end + project.save() + end +end + +build_specify_archs_only() + target 'Runner' do use_frameworks! use_modular_headers! diff --git a/frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj b/frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj index 2e7ab66fee..61cb05b101 100644 --- a/frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj +++ b/frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj @@ -421,7 +421,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - EXCLUDED_ARCHS = arm64; + EXCLUDED_ARCHS = ""; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -553,7 +553,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - EXCLUDED_ARCHS = arm64; + EXCLUDED_ARCHS = ""; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -577,7 +577,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - EXCLUDED_ARCHS = arm64; + EXCLUDED_ARCHS = ""; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/frontend/app_flowy/packages/flowy_infra/lib/text_style.dart b/frontend/app_flowy/packages/flowy_infra/lib/text_style.dart index 26619834bc..9187e17f6e 100644 --- a/frontend/app_flowy/packages/flowy_infra/lib/text_style.dart +++ b/frontend/app_flowy/packages/flowy_infra/lib/text_style.dart @@ -71,7 +71,7 @@ class TextStyles { static TextStyle get CalloutFocus => Callout.bold; // ignore: non_constant_identifier_names - static TextStyle get Btn => quicksand.bold.size(FontSizes.s14).letterSpace(1.75); + static TextStyle get Btn => quicksand.bold.size(FontSizes.s16).letterSpace(1.75); // ignore: non_constant_identifier_names static TextStyle get BtnSelected => quicksand.size(FontSizes.s14).letterSpace(1.75); diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart index 9a7407f9bb..046ee8a0c1 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart @@ -1,4 +1,3 @@ -import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; @@ -28,7 +27,7 @@ class FlowyButton extends StatelessWidget { return InkWell( onTap: onTap, child: FlowyHover( - style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: hoverColor), + style: HoverStyle(borderRadius: BorderRadius.zero, hoverColor: hoverColor), setSelected: () => isSelected, builder: (context, onHover) => _render(), ), 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 bb7144974b..42ee43cabc 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 @@ -1,9 +1,6 @@ import 'package:flutter/material.dart'; // ignore: unused_import import 'package:flowy_infra/time/duration.dart'; -import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/theme.dart'; -import 'package:provider/provider.dart'; typedef HoverBuilder = Widget Function(BuildContext context, bool onHover); @@ -52,7 +49,7 @@ class _FlowyHoverState extends State { child: child, ); } else { - return child; + return Container(child: child, color: widget.style.backgroundColor); } } } @@ -63,12 +60,14 @@ class HoverStyle { final Color hoverColor; final BorderRadius borderRadius; final EdgeInsets contentMargin; + final Color backgroundColor; const HoverStyle( {this.borderColor = Colors.transparent, this.borderWidth = 0, this.borderRadius = const BorderRadius.all(Radius.circular(6)), this.contentMargin = EdgeInsets.zero, + this.backgroundColor = Colors.transparent, required this.hoverColor}); } @@ -100,120 +99,3 @@ class FlowyHoverContainer extends StatelessWidget { ); } } - -// -abstract class FlowyHoverWidget extends Widget { - const FlowyHoverWidget({Key? key}) : super(key: key); - - ValueNotifier? get onFocus; -} - -class FlowyHover2 extends StatefulWidget { - final FlowyHoverWidget child; - final EdgeInsets contentPadding; - const FlowyHover2({ - required this.child, - this.contentPadding = EdgeInsets.zero, - Key? key, - }) : super(key: key); - - @override - State createState() => _FlowyHover2State(); -} - -class _FlowyHover2State extends State { - late FlowyHoverState _hoverState; - VoidCallback? _listenerFn; - - @override - void initState() { - _hoverState = FlowyHoverState(); - - 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(); - } - - @override - Widget build(BuildContext context) { - return ChangeNotifierProvider.value( - value: _hoverState, - child: MouseRegion( - cursor: SystemMouseCursors.click, - opaque: false, - onEnter: (p) => setState(() => _hoverState.onHover = true), - onExit: (p) => setState(() => _hoverState.onHover = false), - child: Stack( - fit: StackFit.loose, - alignment: AlignmentDirectional.center, - children: [ - const _HoverBackground(), - Padding( - padding: widget.contentPadding, - child: widget.child, - ), - ], - ), - ), - ); - } -} - -class _HoverBackground extends StatelessWidget { - const _HoverBackground({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final theme = context.watch(); - return Consumer( - builder: (context, state, child) { - if (state.onHover || state.onFocus) { - return FlowyHoverContainer( - style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: theme.shader6), - ); - } else { - return const SizedBox(); - } - }, - ); - } -} - -class FlowyHoverState extends ChangeNotifier { - bool _onHover = false; - bool _onFocus = false; - - set onHover(bool value) { - if (_onHover != value) { - _onHover = value; - notifyListeners(); - } - } - - bool get onHover => _onHover; - - set onFocus(bool value) { - if (_onFocus != value) { - _onFocus = value; - notifyListeners(); - } - } - - bool get onFocus => _onFocus; -} diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scrollview.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scrollview.dart index da316685dd..bf086f756e 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scrollview.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scrollview.dart @@ -23,7 +23,7 @@ class StyledSingleChildScrollView extends StatefulWidget { this.handleColor, this.controller, this.scrollbarPadding, - this.barSize = 6, + this.barSize = 12, }) : super(key: key); @override diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/primary_button.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/primary_button.dart index 52b2624385..3cd1cdf81e 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/primary_button.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/primary_button.dart @@ -15,7 +15,7 @@ class PrimaryTextButton extends StatelessWidget { @override Widget build(BuildContext context) { - TextStyle txtStyle = TextStyles.Footnote.textColor(Colors.white); + TextStyle txtStyle = TextStyles.Btn.textColor(Colors.white); return PrimaryButton(bigMode: bigMode, onPressed: onPressed, child: Text(label, style: txtStyle)); } } diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart index 77c89fa13e..dedef61295 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart @@ -17,7 +17,7 @@ class SecondaryTextButton extends StatelessWidget { @override Widget build(BuildContext context) { final theme = context.watch(); - TextStyle txtStyle = TextStyles.Footnote.textColor(theme.main1); + TextStyle txtStyle = TextStyles.Btn.textColor(theme.main1); return SecondaryButton(bigMode: bigMode, onPressed: onPressed, child: Text(label, style: txtStyle)); } } diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-text-block/dart_event.dart b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-text-block/dart_event.dart index 6785a1c681..6b9df7ae97 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-text-block/dart_event.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-text-block/dart_event.dart @@ -1,13 +1,13 @@ /// Auto generate. Do not edit part of '../../dispatch.dart'; -class BlockEventGetBlockData { +class TextBlockEventGetBlockData { TextBlockId request; - BlockEventGetBlockData(this.request); + TextBlockEventGetBlockData(this.request); Future> send() { final request = FFIRequest.create() - ..event = BlockEvent.GetBlockData.toString() + ..event = TextBlockEvent.GetBlockData.toString() ..payload = requestToBytes(this.request); return Dispatch.asyncRequest(request) @@ -18,13 +18,13 @@ class BlockEventGetBlockData { } } -class BlockEventApplyDelta { +class TextBlockEventApplyDelta { TextBlockDelta request; - BlockEventApplyDelta(this.request); + TextBlockEventApplyDelta(this.request); Future> send() { final request = FFIRequest.create() - ..event = BlockEvent.ApplyDelta.toString() + ..event = TextBlockEvent.ApplyDelta.toString() ..payload = requestToBytes(this.request); return Dispatch.asyncRequest(request) @@ -35,13 +35,13 @@ class BlockEventApplyDelta { } } -class BlockEventExportDocument { +class TextBlockEventExportDocument { ExportPayload request; - BlockEventExportDocument(this.request); + TextBlockEventExportDocument(this.request); Future> send() { final request = FFIRequest.create() - ..event = BlockEvent.ExportDocument.toString() + ..event = TextBlockEvent.ExportDocument.toString() ..payload = requestToBytes(this.request); return Dispatch.asyncRequest(request) diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/format.pb.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/format.pb.dart new file mode 100644 index 0000000000..6983d0e025 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/format.pb.dart @@ -0,0 +1,11 @@ +/// +// Generated code. Do not modify. +// source: format.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; + +export 'format.pbenum.dart'; + diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/format.pbenum.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/format.pbenum.dart new file mode 100644 index 0000000000..73c544f8a5 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/format.pbenum.dart @@ -0,0 +1,94 @@ +/// +// Generated code. Do not modify. +// source: format.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 + +// ignore_for_file: UNDEFINED_SHOWN_NAME +import 'dart:core' as $core; +import 'package:protobuf/protobuf.dart' as $pb; + +class NumberFormat extends $pb.ProtobufEnum { + static const NumberFormat Number = NumberFormat._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Number'); + static const NumberFormat USD = NumberFormat._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'USD'); + static const NumberFormat CanadianDollar = NumberFormat._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CanadianDollar'); + static const NumberFormat EUR = NumberFormat._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EUR'); + static const NumberFormat Pound = NumberFormat._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Pound'); + static const NumberFormat Yen = NumberFormat._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Yen'); + static const NumberFormat Ruble = NumberFormat._(7, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Ruble'); + static const NumberFormat Rupee = NumberFormat._(8, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Rupee'); + static const NumberFormat Won = NumberFormat._(9, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Won'); + static const NumberFormat Yuan = NumberFormat._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Yuan'); + static const NumberFormat Real = NumberFormat._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Real'); + static const NumberFormat Lira = NumberFormat._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Lira'); + static const NumberFormat Rupiah = NumberFormat._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Rupiah'); + static const NumberFormat Franc = NumberFormat._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Franc'); + static const NumberFormat HongKongDollar = NumberFormat._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'HongKongDollar'); + static const NumberFormat NewZealandDollar = NumberFormat._(16, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NewZealandDollar'); + static const NumberFormat Krona = NumberFormat._(17, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Krona'); + static const NumberFormat NorwegianKrone = NumberFormat._(18, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NorwegianKrone'); + static const NumberFormat MexicanPeso = NumberFormat._(19, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MexicanPeso'); + static const NumberFormat Rand = NumberFormat._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Rand'); + static const NumberFormat NewTaiwanDollar = NumberFormat._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NewTaiwanDollar'); + static const NumberFormat DanishKrone = NumberFormat._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DanishKrone'); + static const NumberFormat Baht = NumberFormat._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Baht'); + static const NumberFormat Forint = NumberFormat._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Forint'); + static const NumberFormat Koruna = NumberFormat._(25, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Koruna'); + static const NumberFormat Shekel = NumberFormat._(26, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Shekel'); + static const NumberFormat ChileanPeso = NumberFormat._(27, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ChileanPeso'); + static const NumberFormat PhilippinePeso = NumberFormat._(28, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PhilippinePeso'); + static const NumberFormat Dirham = NumberFormat._(29, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Dirham'); + static const NumberFormat ColombianPeso = NumberFormat._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ColombianPeso'); + static const NumberFormat Riyal = NumberFormat._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Riyal'); + static const NumberFormat Ringgit = NumberFormat._(32, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Ringgit'); + static const NumberFormat Leu = NumberFormat._(33, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Leu'); + static const NumberFormat ArgentinePeso = NumberFormat._(34, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ArgentinePeso'); + static const NumberFormat UruguayanPeso = NumberFormat._(35, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UruguayanPeso'); + static const NumberFormat Percent = NumberFormat._(36, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Percent'); + + static const $core.List values = [ + Number, + USD, + CanadianDollar, + EUR, + Pound, + Yen, + Ruble, + Rupee, + Won, + Yuan, + Real, + Lira, + Rupiah, + Franc, + HongKongDollar, + NewZealandDollar, + Krona, + NorwegianKrone, + MexicanPeso, + Rand, + NewTaiwanDollar, + DanishKrone, + Baht, + Forint, + Koruna, + Shekel, + ChileanPeso, + PhilippinePeso, + Dirham, + ColombianPeso, + Riyal, + Ringgit, + Leu, + ArgentinePeso, + UruguayanPeso, + Percent, + ]; + + static final $core.Map<$core.int, NumberFormat> _byValue = $pb.ProtobufEnum.initByValue(values); + static NumberFormat? valueOf($core.int value) => _byValue[value]; + + const NumberFormat._($core.int v, $core.String n) : super(v, n); +} + diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/format.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/format.pbjson.dart new file mode 100644 index 0000000000..69071ef303 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/format.pbjson.dart @@ -0,0 +1,55 @@ +/// +// Generated code. Do not modify. +// source: format.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 numberFormatDescriptor instead') +const NumberFormat$json = const { + '1': 'NumberFormat', + '2': const [ + const {'1': 'Number', '2': 0}, + const {'1': 'USD', '2': 1}, + const {'1': 'CanadianDollar', '2': 2}, + const {'1': 'EUR', '2': 4}, + const {'1': 'Pound', '2': 5}, + const {'1': 'Yen', '2': 6}, + const {'1': 'Ruble', '2': 7}, + const {'1': 'Rupee', '2': 8}, + const {'1': 'Won', '2': 9}, + const {'1': 'Yuan', '2': 10}, + const {'1': 'Real', '2': 11}, + const {'1': 'Lira', '2': 12}, + const {'1': 'Rupiah', '2': 13}, + const {'1': 'Franc', '2': 14}, + const {'1': 'HongKongDollar', '2': 15}, + const {'1': 'NewZealandDollar', '2': 16}, + const {'1': 'Krona', '2': 17}, + const {'1': 'NorwegianKrone', '2': 18}, + const {'1': 'MexicanPeso', '2': 19}, + const {'1': 'Rand', '2': 20}, + const {'1': 'NewTaiwanDollar', '2': 21}, + const {'1': 'DanishKrone', '2': 22}, + const {'1': 'Baht', '2': 23}, + const {'1': 'Forint', '2': 24}, + const {'1': 'Koruna', '2': 25}, + const {'1': 'Shekel', '2': 26}, + const {'1': 'ChileanPeso', '2': 27}, + const {'1': 'PhilippinePeso', '2': 28}, + const {'1': 'Dirham', '2': 29}, + const {'1': 'ColombianPeso', '2': 30}, + const {'1': 'Riyal', '2': 31}, + const {'1': 'Ringgit', '2': 32}, + const {'1': 'Leu', '2': 33}, + const {'1': 'ArgentinePeso', '2': 34}, + const {'1': 'UruguayanPeso', '2': 35}, + const {'1': 'Percent', '2': 36}, + ], +}; + +/// Descriptor for `NumberFormat`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List numberFormatDescriptor = $convert.base64Decode('CgxOdW1iZXJGb3JtYXQSCgoGTnVtYmVyEAASBwoDVVNEEAESEgoOQ2FuYWRpYW5Eb2xsYXIQAhIHCgNFVVIQBBIJCgVQb3VuZBAFEgcKA1llbhAGEgkKBVJ1YmxlEAcSCQoFUnVwZWUQCBIHCgNXb24QCRIICgRZdWFuEAoSCAoEUmVhbBALEggKBExpcmEQDBIKCgZSdXBpYWgQDRIJCgVGcmFuYxAOEhIKDkhvbmdLb25nRG9sbGFyEA8SFAoQTmV3WmVhbGFuZERvbGxhchAQEgkKBUtyb25hEBESEgoOTm9yd2VnaWFuS3JvbmUQEhIPCgtNZXhpY2FuUGVzbxATEggKBFJhbmQQFBITCg9OZXdUYWl3YW5Eb2xsYXIQFRIPCgtEYW5pc2hLcm9uZRAWEggKBEJhaHQQFxIKCgZGb3JpbnQQGBIKCgZLb3J1bmEQGRIKCgZTaGVrZWwQGhIPCgtDaGlsZWFuUGVzbxAbEhIKDlBoaWxpcHBpbmVQZXNvEBwSCgoGRGlyaGFtEB0SEQoNQ29sb21iaWFuUGVzbxAeEgkKBVJpeWFsEB8SCwoHUmluZ2dpdBAgEgcKA0xldRAhEhEKDUFyZ2VudGluZVBlc28QIhIRCg1VcnVndWF5YW5QZXNvECMSCwoHUGVyY2VudBAk'); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/format.pbserver.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/format.pbserver.dart new file mode 100644 index 0000000000..f61c8f2533 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/format.pbserver.dart @@ -0,0 +1,9 @@ +/// +// Generated code. Do not modify. +// source: format.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 'format.pb.dart'; + diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pb.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pb.dart index 54f4d9546f..83ded2177b 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pb.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pb.dart @@ -9,13 +9,11 @@ import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; -import 'number_type_option.pbenum.dart'; - -export 'number_type_option.pbenum.dart'; +import 'format.pbenum.dart' as $0; class NumberTypeOption extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'NumberTypeOption', createEmptyInstance: create) - ..e(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'format', $pb.PbFieldType.OE, defaultOrMaker: NumberFormat.Number, valueOf: NumberFormat.valueOf, enumValues: NumberFormat.values) + ..e<$0.NumberFormat>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'format', $pb.PbFieldType.OE, defaultOrMaker: $0.NumberFormat.Number, valueOf: $0.NumberFormat.valueOf, enumValues: $0.NumberFormat.values) ..a<$core.int>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'scale', $pb.PbFieldType.OU3) ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'symbol') ..aOB(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'signPositive') @@ -25,7 +23,7 @@ class NumberTypeOption extends $pb.GeneratedMessage { NumberTypeOption._() : super(); factory NumberTypeOption({ - NumberFormat? format, + $0.NumberFormat? format, $core.int? scale, $core.String? symbol, $core.bool? signPositive, @@ -71,9 +69,9 @@ class NumberTypeOption extends $pb.GeneratedMessage { static NumberTypeOption? _defaultInstance; @$pb.TagNumber(1) - NumberFormat get format => $_getN(0); + $0.NumberFormat get format => $_getN(0); @$pb.TagNumber(1) - set format(NumberFormat v) { setField(1, v); } + set format($0.NumberFormat v) { setField(1, v); } @$pb.TagNumber(1) $core.bool hasFormat() => $_has(0); @$pb.TagNumber(1) diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pbenum.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pbenum.dart index baa7e2f043..06034a2f8b 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pbenum.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pbenum.dart @@ -5,90 +5,3 @@ // @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 -// ignore_for_file: UNDEFINED_SHOWN_NAME -import 'dart:core' as $core; -import 'package:protobuf/protobuf.dart' as $pb; - -class NumberFormat extends $pb.ProtobufEnum { - static const NumberFormat Number = NumberFormat._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Number'); - static const NumberFormat USD = NumberFormat._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'USD'); - static const NumberFormat CanadianDollar = NumberFormat._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CanadianDollar'); - static const NumberFormat EUR = NumberFormat._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EUR'); - static const NumberFormat Pound = NumberFormat._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Pound'); - static const NumberFormat Yen = NumberFormat._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Yen'); - static const NumberFormat Ruble = NumberFormat._(7, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Ruble'); - static const NumberFormat Rupee = NumberFormat._(8, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Rupee'); - static const NumberFormat Won = NumberFormat._(9, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Won'); - static const NumberFormat Yuan = NumberFormat._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Yuan'); - static const NumberFormat Real = NumberFormat._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Real'); - static const NumberFormat Lira = NumberFormat._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Lira'); - static const NumberFormat Rupiah = NumberFormat._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Rupiah'); - static const NumberFormat Franc = NumberFormat._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Franc'); - static const NumberFormat HongKongDollar = NumberFormat._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'HongKongDollar'); - static const NumberFormat NewZealandDollar = NumberFormat._(16, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NewZealandDollar'); - static const NumberFormat Krona = NumberFormat._(17, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Krona'); - static const NumberFormat NorwegianKrone = NumberFormat._(18, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NorwegianKrone'); - static const NumberFormat MexicanPeso = NumberFormat._(19, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MexicanPeso'); - static const NumberFormat Rand = NumberFormat._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Rand'); - static const NumberFormat NewTaiwanDollar = NumberFormat._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NewTaiwanDollar'); - static const NumberFormat DanishKrone = NumberFormat._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DanishKrone'); - static const NumberFormat Baht = NumberFormat._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Baht'); - static const NumberFormat Forint = NumberFormat._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Forint'); - static const NumberFormat Koruna = NumberFormat._(25, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Koruna'); - static const NumberFormat Shekel = NumberFormat._(26, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Shekel'); - static const NumberFormat ChileanPeso = NumberFormat._(27, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ChileanPeso'); - static const NumberFormat PhilippinePeso = NumberFormat._(28, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PhilippinePeso'); - static const NumberFormat Dirham = NumberFormat._(29, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Dirham'); - static const NumberFormat ColombianPeso = NumberFormat._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ColombianPeso'); - static const NumberFormat Riyal = NumberFormat._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Riyal'); - static const NumberFormat Ringgit = NumberFormat._(32, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Ringgit'); - static const NumberFormat Leu = NumberFormat._(33, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Leu'); - static const NumberFormat ArgentinePeso = NumberFormat._(34, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ArgentinePeso'); - static const NumberFormat UruguayanPeso = NumberFormat._(35, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UruguayanPeso'); - static const NumberFormat Percent = NumberFormat._(36, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Percent'); - - static const $core.List values = [ - Number, - USD, - CanadianDollar, - EUR, - Pound, - Yen, - Ruble, - Rupee, - Won, - Yuan, - Real, - Lira, - Rupiah, - Franc, - HongKongDollar, - NewZealandDollar, - Krona, - NorwegianKrone, - MexicanPeso, - Rand, - NewTaiwanDollar, - DanishKrone, - Baht, - Forint, - Koruna, - Shekel, - ChileanPeso, - PhilippinePeso, - Dirham, - ColombianPeso, - Riyal, - Ringgit, - Leu, - ArgentinePeso, - UruguayanPeso, - Percent, - ]; - - static final $core.Map<$core.int, NumberFormat> _byValue = $pb.ProtobufEnum.initByValue(values); - static NumberFormat? valueOf($core.int value) => _byValue[value]; - - const NumberFormat._($core.int v, $core.String n) : super(v, n); -} - diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pbjson.dart index f5e89367e2..492dc3689a 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pbjson.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pbjson.dart @@ -8,51 +8,6 @@ import 'dart:core' as $core; import 'dart:convert' as $convert; import 'dart:typed_data' as $typed_data; -@$core.Deprecated('Use numberFormatDescriptor instead') -const NumberFormat$json = const { - '1': 'NumberFormat', - '2': const [ - const {'1': 'Number', '2': 0}, - const {'1': 'USD', '2': 1}, - const {'1': 'CanadianDollar', '2': 2}, - const {'1': 'EUR', '2': 4}, - const {'1': 'Pound', '2': 5}, - const {'1': 'Yen', '2': 6}, - const {'1': 'Ruble', '2': 7}, - const {'1': 'Rupee', '2': 8}, - const {'1': 'Won', '2': 9}, - const {'1': 'Yuan', '2': 10}, - const {'1': 'Real', '2': 11}, - const {'1': 'Lira', '2': 12}, - const {'1': 'Rupiah', '2': 13}, - const {'1': 'Franc', '2': 14}, - const {'1': 'HongKongDollar', '2': 15}, - const {'1': 'NewZealandDollar', '2': 16}, - const {'1': 'Krona', '2': 17}, - const {'1': 'NorwegianKrone', '2': 18}, - const {'1': 'MexicanPeso', '2': 19}, - const {'1': 'Rand', '2': 20}, - const {'1': 'NewTaiwanDollar', '2': 21}, - const {'1': 'DanishKrone', '2': 22}, - const {'1': 'Baht', '2': 23}, - const {'1': 'Forint', '2': 24}, - const {'1': 'Koruna', '2': 25}, - const {'1': 'Shekel', '2': 26}, - const {'1': 'ChileanPeso', '2': 27}, - const {'1': 'PhilippinePeso', '2': 28}, - const {'1': 'Dirham', '2': 29}, - const {'1': 'ColombianPeso', '2': 30}, - const {'1': 'Riyal', '2': 31}, - const {'1': 'Ringgit', '2': 32}, - const {'1': 'Leu', '2': 33}, - const {'1': 'ArgentinePeso', '2': 34}, - const {'1': 'UruguayanPeso', '2': 35}, - const {'1': 'Percent', '2': 36}, - ], -}; - -/// Descriptor for `NumberFormat`. Decode as a `google.protobuf.EnumDescriptorProto`. -final $typed_data.Uint8List numberFormatDescriptor = $convert.base64Decode('CgxOdW1iZXJGb3JtYXQSCgoGTnVtYmVyEAASBwoDVVNEEAESEgoOQ2FuYWRpYW5Eb2xsYXIQAhIHCgNFVVIQBBIJCgVQb3VuZBAFEgcKA1llbhAGEgkKBVJ1YmxlEAcSCQoFUnVwZWUQCBIHCgNXb24QCRIICgRZdWFuEAoSCAoEUmVhbBALEggKBExpcmEQDBIKCgZSdXBpYWgQDRIJCgVGcmFuYxAOEhIKDkhvbmdLb25nRG9sbGFyEA8SFAoQTmV3WmVhbGFuZERvbGxhchAQEgkKBUtyb25hEBESEgoOTm9yd2VnaWFuS3JvbmUQEhIPCgtNZXhpY2FuUGVzbxATEggKBFJhbmQQFBITCg9OZXdUYWl3YW5Eb2xsYXIQFRIPCgtEYW5pc2hLcm9uZRAWEggKBEJhaHQQFxIKCgZGb3JpbnQQGBIKCgZLb3J1bmEQGRIKCgZTaGVrZWwQGhIPCgtDaGlsZWFuUGVzbxAbEhIKDlBoaWxpcHBpbmVQZXNvEBwSCgoGRGlyaGFtEB0SEQoNQ29sb21iaWFuUGVzbxAeEgkKBVJpeWFsEB8SCwoHUmluZ2dpdBAgEgcKA0xldRAhEhEKDUFyZ2VudGluZVBlc28QIhIRCg1VcnVndWF5YW5QZXNvECMSCwoHUGVyY2VudBAk'); @$core.Deprecated('Use numberTypeOptionDescriptor instead') const NumberTypeOption$json = const { '1': 'NumberTypeOption', 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 c056e2799a..b0cac042fd 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 @@ -7,6 +7,7 @@ export './row_entities.pb.dart'; export './cell_entities.pb.dart'; export './url_type_option.pb.dart'; export './checkbox_type_option.pb.dart'; +export './format.pb.dart'; export './event_map.pb.dart'; export './text_type_option.pb.dart'; export './date_type_option.pb.dart'; diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-text-block/event_map.pbenum.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-text-block/event_map.pbenum.dart index d88c52395c..02414f63fe 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-text-block/event_map.pbenum.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-text-block/event_map.pbenum.dart @@ -9,20 +9,20 @@ import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; -class BlockEvent extends $pb.ProtobufEnum { - static const BlockEvent GetBlockData = BlockEvent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetBlockData'); - static const BlockEvent ApplyDelta = BlockEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ApplyDelta'); - static const BlockEvent ExportDocument = BlockEvent._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ExportDocument'); +class TextBlockEvent extends $pb.ProtobufEnum { + static const TextBlockEvent GetBlockData = TextBlockEvent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetBlockData'); + static const TextBlockEvent ApplyDelta = TextBlockEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ApplyDelta'); + static const TextBlockEvent ExportDocument = TextBlockEvent._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ExportDocument'); - static const $core.List values = [ + static const $core.List values = [ GetBlockData, ApplyDelta, ExportDocument, ]; - static final $core.Map<$core.int, BlockEvent> _byValue = $pb.ProtobufEnum.initByValue(values); - static BlockEvent? valueOf($core.int value) => _byValue[value]; + static final $core.Map<$core.int, TextBlockEvent> _byValue = $pb.ProtobufEnum.initByValue(values); + static TextBlockEvent? valueOf($core.int value) => _byValue[value]; - const BlockEvent._($core.int v, $core.String n) : super(v, n); + const TextBlockEvent._($core.int v, $core.String n) : super(v, n); } diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-text-block/event_map.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-text-block/event_map.pbjson.dart index ac0f243e6f..f4c13be996 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-text-block/event_map.pbjson.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-text-block/event_map.pbjson.dart @@ -8,9 +8,9 @@ import 'dart:core' as $core; import 'dart:convert' as $convert; import 'dart:typed_data' as $typed_data; -@$core.Deprecated('Use blockEventDescriptor instead') -const BlockEvent$json = const { - '1': 'BlockEvent', +@$core.Deprecated('Use textBlockEventDescriptor instead') +const TextBlockEvent$json = const { + '1': 'TextBlockEvent', '2': const [ const {'1': 'GetBlockData', '2': 0}, const {'1': 'ApplyDelta', '2': 1}, @@ -18,5 +18,5 @@ const BlockEvent$json = const { ], }; -/// Descriptor for `BlockEvent`. Decode as a `google.protobuf.EnumDescriptorProto`. -final $typed_data.Uint8List blockEventDescriptor = $convert.base64Decode('CgpCbG9ja0V2ZW50EhAKDEdldEJsb2NrRGF0YRAAEg4KCkFwcGx5RGVsdGEQARISCg5FeHBvcnREb2N1bWVudBAC'); +/// Descriptor for `TextBlockEvent`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List textBlockEventDescriptor = $convert.base64Decode('Cg5UZXh0QmxvY2tFdmVudBIQCgxHZXRCbG9ja0RhdGEQABIOCgpBcHBseURlbHRhEAESEgoORXhwb3J0RG9jdW1lbnQQAg=='); diff --git a/frontend/app_flowy/pubspec.yaml b/frontend/app_flowy/pubspec.yaml index eb7b17dc21..8dbc3b4c70 100644 --- a/frontend/app_flowy/pubspec.yaml +++ b/frontend/app_flowy/pubspec.yaml @@ -72,7 +72,7 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 device_info_plus: ^3.2.1 - fluttertoast: ^8.0.8 + fluttertoast: ^8.0.9 table_calendar: ^3.0.5 reorderables: ^0.5.0 linked_scroll_controller: ^0.2.0 diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 393a02ac73..57cfede8cb 100755 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -937,6 +937,7 @@ dependencies = [ "flowy-revision", "flowy-sync", "flowy-test", + "futures", "indexmap", "lazy_static", "lib-dispatch", diff --git a/frontend/rust-lib/flowy-folder/src/manager.rs b/frontend/rust-lib/flowy-folder/src/manager.rs index fe9a160b39..3181c14625 100644 --- a/frontend/rust-lib/flowy-folder/src/manager.rs +++ b/frontend/rust-lib/flowy-folder/src/manager.rs @@ -241,11 +241,11 @@ pub trait ViewDataProcessor { fn close_container(&self, view_id: &str) -> FutureResult<(), FlowyError>; - fn delta_bytes(&self, view_id: &str) -> FutureResult; + fn view_delta_data(&self, view_id: &str) -> FutureResult; fn create_default_view(&self, user_id: &str, view_id: &str) -> FutureResult; - fn process_create_view_data(&self, user_id: &str, view_id: &str, data: Vec) -> FutureResult; + fn process_view_delta_data(&self, user_id: &str, view_id: &str, data: Vec) -> FutureResult; fn data_type(&self) -> ViewDataType; } 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 ae88073afc..4de3b306d8 100644 --- a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/view/controller.rs @@ -60,7 +60,7 @@ impl ViewController { params.data = view_data.to_vec(); } else { let delta_data = processor - .process_create_view_data(&user_id, ¶ms.view_id, params.data.clone()) + .process_view_delta_data(&user_id, ¶ms.view_id, params.data.clone()) .await?; let _ = self .create_view(¶ms.view_id, params.data_type.clone(), delta_data) @@ -176,7 +176,7 @@ impl ViewController { .await?; let processor = self.get_data_processor(&view.data_type)?; - let delta_bytes = processor.delta_bytes(view_id).await?; + let delta_bytes = processor.view_delta_data(view_id).await?; let duplicate_params = CreateViewParams { belong_to_id: view.belong_to_id.clone(), name: format!("{} (copy)", &view.name), @@ -238,7 +238,7 @@ impl ViewController { } impl ViewController { - #[tracing::instrument(level = "debug", skip(self), err)] + #[tracing::instrument(level = "debug", skip(self, params), err)] async fn create_view_on_server(&self, params: CreateViewParams) -> Result { let token = self.user.token()?; let view = self.cloud_service.create_view(&token, params).await?; diff --git a/frontend/rust-lib/flowy-grid/Cargo.toml b/frontend/rust-lib/flowy-grid/Cargo.toml index 43b0cbf69f..1cf2ee0e53 100644 --- a/frontend/rust-lib/flowy-grid/Cargo.toml +++ b/frontend/rust-lib/flowy-grid/Cargo.toml @@ -37,6 +37,7 @@ serde_repr = "0.1" indexmap = {version = "1.8.1", features = ["serde"]} fancy-regex = "0.10.0" url = { version = "2"} +futures = "0.3.15" [dev-dependencies] flowy-test = { path = "../flowy-test" } diff --git a/frontend/rust-lib/flowy-grid/src/manager.rs b/frontend/rust-lib/flowy-grid/src/manager.rs index f8ea9e70ae..2965cb5bd9 100644 --- a/frontend/rust-lib/flowy-grid/src/manager.rs +++ b/frontend/rust-lib/flowy-grid/src/manager.rs @@ -154,11 +154,10 @@ pub async fn make_grid_view_data( grid_manager: Arc, build_context: BuildGridContext, ) -> FlowyResult { - let block_id = build_context.block_meta.block_id.clone(); let grid_meta = GridMeta { grid_id: view_id.to_string(), fields: build_context.field_metas, - blocks: vec![build_context.block_meta], + blocks: build_context.blocks, }; // Create grid @@ -168,19 +167,23 @@ pub async fn make_grid_view_data( Revision::initial_revision(user_id, view_id, grid_delta_data.clone()).into(); let _ = grid_manager.create_grid(view_id, repeated_revision).await?; - // Indexing the block's rows - build_context.block_meta_data.rows.iter().for_each(|row| { - let _ = grid_manager.block_index_cache.insert(&row.block_id, &row.id); - }); + for block_meta_data in build_context.blocks_meta_data { + let block_id = block_meta_data.block_id.clone(); - // Create grid's block - let grid_block_meta_delta = make_block_meta_delta(&build_context.block_meta_data); - let block_meta_delta_data = grid_block_meta_delta.to_delta_bytes(); - let repeated_revision: RepeatedRevision = - Revision::initial_revision(user_id, &block_id, block_meta_delta_data).into(); - let _ = grid_manager - .create_grid_block_meta(&block_id, repeated_revision) - .await?; + // Indexing the block's rows + block_meta_data.rows.iter().for_each(|row| { + let _ = grid_manager.block_index_cache.insert(&row.block_id, &row.id); + }); + + // Create grid's block + let grid_block_meta_delta = make_block_meta_delta(&block_meta_data); + let block_meta_delta_data = grid_block_meta_delta.to_delta_bytes(); + let repeated_revision: RepeatedRevision = + Revision::initial_revision(user_id, &block_id, block_meta_delta_data).into(); + let _ = grid_manager + .create_grid_block_meta(&block_id, repeated_revision) + .await?; + } Ok(grid_delta_data) } diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/model/format.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/format.rs new file mode 100644 index 0000000000..bba24f9e4c --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/format.rs @@ -0,0 +1,207 @@ +// 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 `format.proto` + +/// Generated files are compatible only with the same version +/// of protobuf runtime. +// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2; + +#[derive(Clone,PartialEq,Eq,Debug,Hash)] +pub enum NumberFormat { + Number = 0, + USD = 1, + CanadianDollar = 2, + EUR = 4, + Pound = 5, + Yen = 6, + Ruble = 7, + Rupee = 8, + Won = 9, + Yuan = 10, + Real = 11, + Lira = 12, + Rupiah = 13, + Franc = 14, + HongKongDollar = 15, + NewZealandDollar = 16, + Krona = 17, + NorwegianKrone = 18, + MexicanPeso = 19, + Rand = 20, + NewTaiwanDollar = 21, + DanishKrone = 22, + Baht = 23, + Forint = 24, + Koruna = 25, + Shekel = 26, + ChileanPeso = 27, + PhilippinePeso = 28, + Dirham = 29, + ColombianPeso = 30, + Riyal = 31, + Ringgit = 32, + Leu = 33, + ArgentinePeso = 34, + UruguayanPeso = 35, + Percent = 36, +} + +impl ::protobuf::ProtobufEnum for NumberFormat { + fn value(&self) -> i32 { + *self as i32 + } + + fn from_i32(value: i32) -> ::std::option::Option { + match value { + 0 => ::std::option::Option::Some(NumberFormat::Number), + 1 => ::std::option::Option::Some(NumberFormat::USD), + 2 => ::std::option::Option::Some(NumberFormat::CanadianDollar), + 4 => ::std::option::Option::Some(NumberFormat::EUR), + 5 => ::std::option::Option::Some(NumberFormat::Pound), + 6 => ::std::option::Option::Some(NumberFormat::Yen), + 7 => ::std::option::Option::Some(NumberFormat::Ruble), + 8 => ::std::option::Option::Some(NumberFormat::Rupee), + 9 => ::std::option::Option::Some(NumberFormat::Won), + 10 => ::std::option::Option::Some(NumberFormat::Yuan), + 11 => ::std::option::Option::Some(NumberFormat::Real), + 12 => ::std::option::Option::Some(NumberFormat::Lira), + 13 => ::std::option::Option::Some(NumberFormat::Rupiah), + 14 => ::std::option::Option::Some(NumberFormat::Franc), + 15 => ::std::option::Option::Some(NumberFormat::HongKongDollar), + 16 => ::std::option::Option::Some(NumberFormat::NewZealandDollar), + 17 => ::std::option::Option::Some(NumberFormat::Krona), + 18 => ::std::option::Option::Some(NumberFormat::NorwegianKrone), + 19 => ::std::option::Option::Some(NumberFormat::MexicanPeso), + 20 => ::std::option::Option::Some(NumberFormat::Rand), + 21 => ::std::option::Option::Some(NumberFormat::NewTaiwanDollar), + 22 => ::std::option::Option::Some(NumberFormat::DanishKrone), + 23 => ::std::option::Option::Some(NumberFormat::Baht), + 24 => ::std::option::Option::Some(NumberFormat::Forint), + 25 => ::std::option::Option::Some(NumberFormat::Koruna), + 26 => ::std::option::Option::Some(NumberFormat::Shekel), + 27 => ::std::option::Option::Some(NumberFormat::ChileanPeso), + 28 => ::std::option::Option::Some(NumberFormat::PhilippinePeso), + 29 => ::std::option::Option::Some(NumberFormat::Dirham), + 30 => ::std::option::Option::Some(NumberFormat::ColombianPeso), + 31 => ::std::option::Option::Some(NumberFormat::Riyal), + 32 => ::std::option::Option::Some(NumberFormat::Ringgit), + 33 => ::std::option::Option::Some(NumberFormat::Leu), + 34 => ::std::option::Option::Some(NumberFormat::ArgentinePeso), + 35 => ::std::option::Option::Some(NumberFormat::UruguayanPeso), + 36 => ::std::option::Option::Some(NumberFormat::Percent), + _ => ::std::option::Option::None + } + } + + fn values() -> &'static [Self] { + static values: &'static [NumberFormat] = &[ + NumberFormat::Number, + NumberFormat::USD, + NumberFormat::CanadianDollar, + NumberFormat::EUR, + NumberFormat::Pound, + NumberFormat::Yen, + NumberFormat::Ruble, + NumberFormat::Rupee, + NumberFormat::Won, + NumberFormat::Yuan, + NumberFormat::Real, + NumberFormat::Lira, + NumberFormat::Rupiah, + NumberFormat::Franc, + NumberFormat::HongKongDollar, + NumberFormat::NewZealandDollar, + NumberFormat::Krona, + NumberFormat::NorwegianKrone, + NumberFormat::MexicanPeso, + NumberFormat::Rand, + NumberFormat::NewTaiwanDollar, + NumberFormat::DanishKrone, + NumberFormat::Baht, + NumberFormat::Forint, + NumberFormat::Koruna, + NumberFormat::Shekel, + NumberFormat::ChileanPeso, + NumberFormat::PhilippinePeso, + NumberFormat::Dirham, + NumberFormat::ColombianPeso, + NumberFormat::Riyal, + NumberFormat::Ringgit, + NumberFormat::Leu, + NumberFormat::ArgentinePeso, + NumberFormat::UruguayanPeso, + NumberFormat::Percent, + ]; + values + } + + fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor { + static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT; + descriptor.get(|| { + ::protobuf::reflect::EnumDescriptor::new_pb_name::("NumberFormat", file_descriptor_proto()) + }) + } +} + +impl ::std::marker::Copy for NumberFormat { +} + +impl ::std::default::Default for NumberFormat { + fn default() -> Self { + NumberFormat::Number + } +} + +impl ::protobuf::reflect::ProtobufValue for NumberFormat { + fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef { + ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self)) + } +} + +static file_descriptor_proto_data: &'static [u8] = b"\ + \n\x0cformat.proto*\xf8\x03\n\x0cNumberFormat\x12\n\n\x06Number\x10\0\ + \x12\x07\n\x03USD\x10\x01\x12\x12\n\x0eCanadianDollar\x10\x02\x12\x07\n\ + \x03EUR\x10\x04\x12\t\n\x05Pound\x10\x05\x12\x07\n\x03Yen\x10\x06\x12\t\ + \n\x05Ruble\x10\x07\x12\t\n\x05Rupee\x10\x08\x12\x07\n\x03Won\x10\t\x12\ + \x08\n\x04Yuan\x10\n\x12\x08\n\x04Real\x10\x0b\x12\x08\n\x04Lira\x10\x0c\ + \x12\n\n\x06Rupiah\x10\r\x12\t\n\x05Franc\x10\x0e\x12\x12\n\x0eHongKongD\ + ollar\x10\x0f\x12\x14\n\x10NewZealandDollar\x10\x10\x12\t\n\x05Krona\x10\ + \x11\x12\x12\n\x0eNorwegianKrone\x10\x12\x12\x0f\n\x0bMexicanPeso\x10\ + \x13\x12\x08\n\x04Rand\x10\x14\x12\x13\n\x0fNewTaiwanDollar\x10\x15\x12\ + \x0f\n\x0bDanishKrone\x10\x16\x12\x08\n\x04Baht\x10\x17\x12\n\n\x06Forin\ + t\x10\x18\x12\n\n\x06Koruna\x10\x19\x12\n\n\x06Shekel\x10\x1a\x12\x0f\n\ + \x0bChileanPeso\x10\x1b\x12\x12\n\x0ePhilippinePeso\x10\x1c\x12\n\n\x06D\ + irham\x10\x1d\x12\x11\n\rColombianPeso\x10\x1e\x12\t\n\x05Riyal\x10\x1f\ + \x12\x0b\n\x07Ringgit\x10\x20\x12\x07\n\x03Leu\x10!\x12\x11\n\rArgentine\ + Peso\x10\"\x12\x11\n\rUruguayanPeso\x10#\x12\x0b\n\x07Percent\x10$b\x06p\ + roto3\ +"; + +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/model/mod.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs index c0f74e1e9c..8c29b9015a 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs @@ -25,6 +25,9 @@ pub use url_type_option::*; mod checkbox_type_option; pub use checkbox_type_option::*; +mod format; +pub use format::*; + mod event_map; pub use event_map::*; diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/model/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/number_type_option.rs index 790f3eaaf3..8f3c0e70af 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/model/number_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/number_type_option.rs @@ -26,7 +26,7 @@ #[derive(PartialEq,Clone,Default)] pub struct NumberTypeOption { // message fields - pub format: NumberFormat, + pub format: super::format::NumberFormat, pub scale: u32, pub symbol: ::std::string::String, pub sign_positive: bool, @@ -50,15 +50,15 @@ impl NumberTypeOption { // .NumberFormat format = 1; - pub fn get_format(&self) -> NumberFormat { + pub fn get_format(&self) -> super::format::NumberFormat { self.format } pub fn clear_format(&mut self) { - self.format = NumberFormat::Number; + self.format = super::format::NumberFormat::Number; } // Param is passed by value, moved - pub fn set_format(&mut self, v: NumberFormat) { + pub fn set_format(&mut self, v: super::format::NumberFormat) { self.format = v; } @@ -189,7 +189,7 @@ impl ::protobuf::Message for NumberTypeOption { #[allow(unused_variables)] fn compute_size(&self) -> u32 { let mut my_size = 0; - if self.format != NumberFormat::Number { + if self.format != super::format::NumberFormat::Number { my_size += ::protobuf::rt::enum_size(1, self.format); } if self.scale != 0 { @@ -210,7 +210,7 @@ impl ::protobuf::Message for NumberTypeOption { } fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> { - if self.format != NumberFormat::Number { + if self.format != super::format::NumberFormat::Number { os.write_enum(1, ::protobuf::ProtobufEnum::value(&self.format))?; } if self.scale != 0 { @@ -263,7 +263,7 @@ impl ::protobuf::Message for NumberTypeOption { 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::ProtobufTypeEnum>( + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum>( "format", |m: &NumberTypeOption| { &m.format }, |m: &mut NumberTypeOption| { &mut m.format }, @@ -304,7 +304,7 @@ impl ::protobuf::Message for NumberTypeOption { impl ::protobuf::Clear for NumberTypeOption { fn clear(&mut self) { - self.format = NumberFormat::Number; + self.format = super::format::NumberFormat::Number; self.scale = 0; self.symbol.clear(); self.sign_positive = false; @@ -325,179 +325,13 @@ impl ::protobuf::reflect::ProtobufValue for NumberTypeOption { } } -#[derive(Clone,PartialEq,Eq,Debug,Hash)] -pub enum NumberFormat { - Number = 0, - USD = 1, - CanadianDollar = 2, - EUR = 4, - Pound = 5, - Yen = 6, - Ruble = 7, - Rupee = 8, - Won = 9, - Yuan = 10, - Real = 11, - Lira = 12, - Rupiah = 13, - Franc = 14, - HongKongDollar = 15, - NewZealandDollar = 16, - Krona = 17, - NorwegianKrone = 18, - MexicanPeso = 19, - Rand = 20, - NewTaiwanDollar = 21, - DanishKrone = 22, - Baht = 23, - Forint = 24, - Koruna = 25, - Shekel = 26, - ChileanPeso = 27, - PhilippinePeso = 28, - Dirham = 29, - ColombianPeso = 30, - Riyal = 31, - Ringgit = 32, - Leu = 33, - ArgentinePeso = 34, - UruguayanPeso = 35, - Percent = 36, -} - -impl ::protobuf::ProtobufEnum for NumberFormat { - fn value(&self) -> i32 { - *self as i32 - } - - fn from_i32(value: i32) -> ::std::option::Option { - match value { - 0 => ::std::option::Option::Some(NumberFormat::Number), - 1 => ::std::option::Option::Some(NumberFormat::USD), - 2 => ::std::option::Option::Some(NumberFormat::CanadianDollar), - 4 => ::std::option::Option::Some(NumberFormat::EUR), - 5 => ::std::option::Option::Some(NumberFormat::Pound), - 6 => ::std::option::Option::Some(NumberFormat::Yen), - 7 => ::std::option::Option::Some(NumberFormat::Ruble), - 8 => ::std::option::Option::Some(NumberFormat::Rupee), - 9 => ::std::option::Option::Some(NumberFormat::Won), - 10 => ::std::option::Option::Some(NumberFormat::Yuan), - 11 => ::std::option::Option::Some(NumberFormat::Real), - 12 => ::std::option::Option::Some(NumberFormat::Lira), - 13 => ::std::option::Option::Some(NumberFormat::Rupiah), - 14 => ::std::option::Option::Some(NumberFormat::Franc), - 15 => ::std::option::Option::Some(NumberFormat::HongKongDollar), - 16 => ::std::option::Option::Some(NumberFormat::NewZealandDollar), - 17 => ::std::option::Option::Some(NumberFormat::Krona), - 18 => ::std::option::Option::Some(NumberFormat::NorwegianKrone), - 19 => ::std::option::Option::Some(NumberFormat::MexicanPeso), - 20 => ::std::option::Option::Some(NumberFormat::Rand), - 21 => ::std::option::Option::Some(NumberFormat::NewTaiwanDollar), - 22 => ::std::option::Option::Some(NumberFormat::DanishKrone), - 23 => ::std::option::Option::Some(NumberFormat::Baht), - 24 => ::std::option::Option::Some(NumberFormat::Forint), - 25 => ::std::option::Option::Some(NumberFormat::Koruna), - 26 => ::std::option::Option::Some(NumberFormat::Shekel), - 27 => ::std::option::Option::Some(NumberFormat::ChileanPeso), - 28 => ::std::option::Option::Some(NumberFormat::PhilippinePeso), - 29 => ::std::option::Option::Some(NumberFormat::Dirham), - 30 => ::std::option::Option::Some(NumberFormat::ColombianPeso), - 31 => ::std::option::Option::Some(NumberFormat::Riyal), - 32 => ::std::option::Option::Some(NumberFormat::Ringgit), - 33 => ::std::option::Option::Some(NumberFormat::Leu), - 34 => ::std::option::Option::Some(NumberFormat::ArgentinePeso), - 35 => ::std::option::Option::Some(NumberFormat::UruguayanPeso), - 36 => ::std::option::Option::Some(NumberFormat::Percent), - _ => ::std::option::Option::None - } - } - - fn values() -> &'static [Self] { - static values: &'static [NumberFormat] = &[ - NumberFormat::Number, - NumberFormat::USD, - NumberFormat::CanadianDollar, - NumberFormat::EUR, - NumberFormat::Pound, - NumberFormat::Yen, - NumberFormat::Ruble, - NumberFormat::Rupee, - NumberFormat::Won, - NumberFormat::Yuan, - NumberFormat::Real, - NumberFormat::Lira, - NumberFormat::Rupiah, - NumberFormat::Franc, - NumberFormat::HongKongDollar, - NumberFormat::NewZealandDollar, - NumberFormat::Krona, - NumberFormat::NorwegianKrone, - NumberFormat::MexicanPeso, - NumberFormat::Rand, - NumberFormat::NewTaiwanDollar, - NumberFormat::DanishKrone, - NumberFormat::Baht, - NumberFormat::Forint, - NumberFormat::Koruna, - NumberFormat::Shekel, - NumberFormat::ChileanPeso, - NumberFormat::PhilippinePeso, - NumberFormat::Dirham, - NumberFormat::ColombianPeso, - NumberFormat::Riyal, - NumberFormat::Ringgit, - NumberFormat::Leu, - NumberFormat::ArgentinePeso, - NumberFormat::UruguayanPeso, - NumberFormat::Percent, - ]; - values - } - - fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor { - static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT; - descriptor.get(|| { - ::protobuf::reflect::EnumDescriptor::new_pb_name::("NumberFormat", file_descriptor_proto()) - }) - } -} - -impl ::std::marker::Copy for NumberFormat { -} - -impl ::std::default::Default for NumberFormat { - fn default() -> Self { - NumberFormat::Number - } -} - -impl ::protobuf::reflect::ProtobufValue for NumberFormat { - fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef { - ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self)) - } -} - static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x18number_type_option.proto\"\xa0\x01\n\x10NumberTypeOption\x12%\n\ - \x06format\x18\x01\x20\x01(\x0e2\r.NumberFormatR\x06format\x12\x14\n\x05\ - scale\x18\x02\x20\x01(\rR\x05scale\x12\x16\n\x06symbol\x18\x03\x20\x01(\ - \tR\x06symbol\x12#\n\rsign_positive\x18\x04\x20\x01(\x08R\x0csignPositiv\ - e\x12\x12\n\x04name\x18\x05\x20\x01(\tR\x04name*\xf8\x03\n\x0cNumberForm\ - at\x12\n\n\x06Number\x10\0\x12\x07\n\x03USD\x10\x01\x12\x12\n\x0eCanadia\ - nDollar\x10\x02\x12\x07\n\x03EUR\x10\x04\x12\t\n\x05Pound\x10\x05\x12\ - \x07\n\x03Yen\x10\x06\x12\t\n\x05Ruble\x10\x07\x12\t\n\x05Rupee\x10\x08\ - \x12\x07\n\x03Won\x10\t\x12\x08\n\x04Yuan\x10\n\x12\x08\n\x04Real\x10\ - \x0b\x12\x08\n\x04Lira\x10\x0c\x12\n\n\x06Rupiah\x10\r\x12\t\n\x05Franc\ - \x10\x0e\x12\x12\n\x0eHongKongDollar\x10\x0f\x12\x14\n\x10NewZealandDoll\ - ar\x10\x10\x12\t\n\x05Krona\x10\x11\x12\x12\n\x0eNorwegianKrone\x10\x12\ - \x12\x0f\n\x0bMexicanPeso\x10\x13\x12\x08\n\x04Rand\x10\x14\x12\x13\n\ - \x0fNewTaiwanDollar\x10\x15\x12\x0f\n\x0bDanishKrone\x10\x16\x12\x08\n\ - \x04Baht\x10\x17\x12\n\n\x06Forint\x10\x18\x12\n\n\x06Koruna\x10\x19\x12\ - \n\n\x06Shekel\x10\x1a\x12\x0f\n\x0bChileanPeso\x10\x1b\x12\x12\n\x0ePhi\ - lippinePeso\x10\x1c\x12\n\n\x06Dirham\x10\x1d\x12\x11\n\rColombianPeso\ - \x10\x1e\x12\t\n\x05Riyal\x10\x1f\x12\x0b\n\x07Ringgit\x10\x20\x12\x07\n\ - \x03Leu\x10!\x12\x11\n\rArgentinePeso\x10\"\x12\x11\n\rUruguayanPeso\x10\ - #\x12\x0b\n\x07Percent\x10$b\x06proto3\ + \n\x18number_type_option.proto\x1a\x0cformat.proto\"\xa0\x01\n\x10Number\ + TypeOption\x12%\n\x06format\x18\x01\x20\x01(\x0e2\r.NumberFormatR\x06for\ + mat\x12\x14\n\x05scale\x18\x02\x20\x01(\rR\x05scale\x12\x16\n\x06symbol\ + \x18\x03\x20\x01(\tR\x06symbol\x12#\n\rsign_positive\x18\x04\x20\x01(\ + \x08R\x0csignPositive\x12\x12\n\x04name\x18\x05\x20\x01(\tR\x04nameb\x06\ + proto3\ "; 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/proto/format.proto b/frontend/rust-lib/flowy-grid/src/protobuf/proto/format.proto new file mode 100644 index 0000000000..6a1e497aaf --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/protobuf/proto/format.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +enum NumberFormat { + Number = 0; + USD = 1; + CanadianDollar = 2; + EUR = 4; + Pound = 5; + Yen = 6; + Ruble = 7; + Rupee = 8; + Won = 9; + Yuan = 10; + Real = 11; + Lira = 12; + Rupiah = 13; + Franc = 14; + HongKongDollar = 15; + NewZealandDollar = 16; + Krona = 17; + NorwegianKrone = 18; + MexicanPeso = 19; + Rand = 20; + NewTaiwanDollar = 21; + DanishKrone = 22; + Baht = 23; + Forint = 24; + Koruna = 25; + Shekel = 26; + ChileanPeso = 27; + PhilippinePeso = 28; + Dirham = 29; + ColombianPeso = 30; + Riyal = 31; + Ringgit = 32; + Leu = 33; + ArgentinePeso = 34; + UruguayanPeso = 35; + Percent = 36; +} diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/proto/number_type_option.proto b/frontend/rust-lib/flowy-grid/src/protobuf/proto/number_type_option.proto index af08761ad9..47a76e40cd 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/proto/number_type_option.proto +++ b/frontend/rust-lib/flowy-grid/src/protobuf/proto/number_type_option.proto @@ -1,4 +1,5 @@ syntax = "proto3"; +import "format.proto"; message NumberTypeOption { NumberFormat format = 1; @@ -7,41 +8,3 @@ message NumberTypeOption { bool sign_positive = 4; string name = 5; } -enum NumberFormat { - Number = 0; - USD = 1; - CanadianDollar = 2; - EUR = 4; - Pound = 5; - Yen = 6; - Ruble = 7; - Rupee = 8; - Won = 9; - Yuan = 10; - Real = 11; - Lira = 12; - Rupiah = 13; - Franc = 14; - HongKongDollar = 15; - NewZealandDollar = 16; - Krona = 17; - NorwegianKrone = 18; - MexicanPeso = 19; - Rand = 20; - NewTaiwanDollar = 21; - DanishKrone = 22; - Baht = 23; - Forint = 24; - Koruna = 25; - Shekel = 26; - ChileanPeso = 27; - PhilippinePeso = 28; - Dirham = 29; - ColombianPeso = 30; - Riyal = 31; - Ringgit = 32; - Leu = 33; - ArgentinePeso = 34; - UruguayanPeso = 35; - Percent = 36; -} 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 d1cb847b37..d37c869156 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 @@ -1,6 +1,6 @@ use bytes::Bytes; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::entities::{CellMeta, RowMeta, RowMetaChangeset, RowOrder}; +use flowy_grid_data_model::entities::{CellMeta, GridBlockMetaData, RowMeta, RowMetaChangeset, RowOrder}; use flowy_revision::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder}; use flowy_sync::client_grid::{GridBlockMetaChange, GridBlockMetaPad}; use flowy_sync::entities::revision::Revision; @@ -41,6 +41,10 @@ impl GridBlockMetaEditor { }) } + pub async fn duplicate_block_meta_data(&self, duplicated_block_id: &str) -> GridBlockMetaData { + self.pad.read().await.duplicate_data(duplicated_block_id).await + } + /// return current number of rows and the inserted index. The inserted index will be None if the start_row_id is None pub(crate) async fn create_row( &self, 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 6ce36488c2..ec7c47f069 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 @@ -47,7 +47,7 @@ impl GridBlockManager { debug_assert!(!block_id.is_empty()); match self.block_editor_map.get(block_id) { None => { - tracing::error!("The is a fatal error, block is not exist"); + tracing::error!("This is a fatal error, block with id:{} is not exist", block_id); let editor = Arc::new(make_block_meta_editor(&self.user, block_id).await?); self.block_editor_map.insert(block_id.to_owned(), editor.clone()); Ok(editor) @@ -267,6 +267,7 @@ async fn make_block_meta_editor_map( } async fn make_block_meta_editor(user: &Arc, block_id: &str) -> FlowyResult { + tracing::trace!("Open block:{} meta editor", block_id); let token = user.token()?; let user_id = user.user_id()?; let pool = user.db_pool()?; 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 c8d14b6b03..995a0e5e33 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 @@ -42,7 +42,7 @@ impl_type_option!(CheckboxTypeOption, FieldType::Checkbox); const YES: &str = "Yes"; const NO: &str = "No"; -impl CellDataOperation for CheckboxTypeOption { +impl CellDataOperation for CheckboxTypeOption { fn decode_cell_data( &self, encoded_data: T, 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 c96166272a..4a64fc3eca 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,17 +1,16 @@ use crate::entities::{CellIdentifier, CellIdentifierPayload}; use crate::impl_type_option; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, EncodedCellData}; +use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData}; use bytes::Bytes; use chrono::format::strftime::StrftimeItems; -use chrono::NaiveDateTime; +use chrono::{NaiveDateTime, Timelike}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult}; +use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{ CellChangeset, CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, }; use serde::{Deserialize, Serialize}; -use std::str::FromStr; use strum_macros::EnumIter; // Date @@ -29,35 +28,36 @@ pub struct DateTypeOption { impl_type_option!(DateTypeOption, FieldType::DateTime); impl DateTypeOption { - fn today_desc_from_timestamp(&self, timestamp: i64, time: &Option) -> String { - let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0); - self.today_desc_from_native(native, time) - } - #[allow(dead_code)] - fn today_desc_from_str(&self, s: String, time: &Option) -> String { - match NaiveDateTime::parse_from_str(&s, &self.date_fmt(time)) { - Ok(native) => self.today_desc_from_native(native, time), - Err(_) => "".to_owned(), + pub fn new() -> Self { + Self::default() + } + + fn today_desc_from_timestamp(&self, timestamp: i64) -> DateCellData { + let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0); + self.date_from_native(native) + } + + fn date_from_native(&self, native: chrono::NaiveDateTime) -> DateCellData { + if native.timestamp() == 0 { + return DateCellData::default(); } - } - fn today_desc_from_native(&self, native: chrono::NaiveDateTime, time: &Option) -> String { + let time = native.time(); + let has_time = time.hour() != 0 || time.second() != 0; + let utc = self.utc_date_time_from_native(native); - // let china_timezone = FixedOffset::east(8 * 3600); - // let a = utc.with_timezone(&china_timezone); - let fmt = self.date_fmt(time); - let output = format!("{}", utc.format_with_items(StrftimeItems::new(&fmt))); - output - } + let fmt = self.date_format.format_str(); + let date = format!("{}", utc.format_with_items(StrftimeItems::new(fmt))); - fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime { - let native = NaiveDateTime::from_timestamp(timestamp, 0); - self.utc_date_time_from_native(native) - } + let mut time = "".to_string(); + if has_time { + let fmt = format!("{} {}", self.date_format.format_str(), self.time_format.format_str()); + time = format!("{}", utc.format_with_items(StrftimeItems::new(&fmt))).replace(&date, ""); + } - fn utc_date_time_from_native(&self, naive: chrono::NaiveDateTime) -> chrono::DateTime { - chrono::DateTime::::from_utc(naive, chrono::Utc) + let timestamp = native.timestamp(); + DateCellData { date, time, timestamp } } fn date_fmt(&self, time: &Option) -> String { @@ -77,14 +77,6 @@ impl DateTypeOption { } } - fn date_desc_from_timestamp(&self, serde_cell_data: &DateCellDataSerde) -> String { - if serde_cell_data.timestamp == 0 { - return "".to_owned(); - } - - self.today_desc_from_timestamp(serde_cell_data.timestamp, &serde_cell_data.time) - } - fn timestamp_from_utc_with_time( &self, utc: &chrono::DateTime, @@ -113,9 +105,18 @@ impl DateTypeOption { Ok(utc.timestamp()) } + + fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime { + let native = NaiveDateTime::from_timestamp(timestamp, 0); + self.utc_date_time_from_native(native) + } + + fn utc_date_time_from_native(&self, naive: chrono::NaiveDateTime) -> chrono::DateTime { + chrono::DateTime::::from_utc(naive, chrono::Utc) + } } -impl CellDataOperation, DateCellDataSerde> for DateTypeOption { +impl CellDataOperation for DateTypeOption { fn decode_cell_data( &self, encoded_data: T, @@ -123,7 +124,7 @@ impl CellDataOperation, DateCellDataSerde> fo _field_meta: &FieldMeta, ) -> FlowyResult where - T: Into>, + T: Into, { // Return default data if the type_option_cell_data is not FieldType::DateTime. // It happens when switching from one field to another. @@ -133,33 +134,29 @@ impl CellDataOperation, DateCellDataSerde> fo return Ok(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 }) + let timestamp = encoded_data.into().parse::().unwrap_or(0); + let date = self.today_desc_from_timestamp(timestamp); + DecodedCellData::try_from_bytes(date) } - fn apply_changeset(&self, changeset: C, _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(), + None => 0, Some(date_timestamp) => match (self.include_time, content_changeset.time) { (true, Some(time)) => { let time = Some(time.trim().to_uppercase()); let utc = self.utc_date_time_from_timestamp(date_timestamp); - let timestamp = self.timestamp_from_utc_with_time(&utc, &time)?; - DateCellDataSerde::new(timestamp, time, &self.time_format) + self.timestamp_from_utc_with_time(&utc, &time)? } - _ => DateCellDataSerde::from_timestamp(date_timestamp, Some(default_time_str(&self.time_format))), + _ => date_timestamp, }, }; - Ok(cell_data) + Ok(cell_data.to_string()) } } @@ -283,46 +280,6 @@ pub struct DateCellData { pub timestamp: i64, } -#[derive(Default, Serialize, Deserialize)] -pub struct DateCellDataSerde { - pub timestamp: i64, - pub time: Option, -} - -impl DateCellDataSerde { - fn new(timestamp: i64, time: Option, time_format: &TimeFormat) -> Self { - Self { - timestamp, - time: Some(time.unwrap_or_else(|| default_time_str(time_format))), - } - } - - pub(crate) fn from_timestamp(timestamp: i64, time: Option) -> Self { - Self { timestamp, time } - } -} - -impl FromStr for DateCellDataSerde { - type Err = FlowyError; - - 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(), - TimeFormat::TwentyFourHour => "00:00".to_string(), - } -} - #[derive(Clone, Debug, Default, ProtoBuf)] pub struct DateChangesetPayload { #[pb(index = 1)] @@ -399,15 +356,13 @@ impl std::convert::From for CellContentChangeset { #[cfg(test)] mod tests { use crate::services::field::FieldBuilder; - use crate::services::field::{ - DateCellContentChangeset, DateCellData, DateCellDataSerde, DateFormat, DateTypeOption, TimeFormat, - }; - use crate::services::row::{CellDataOperation, EncodedCellData}; - use flowy_grid_data_model::entities::{FieldMeta, FieldType}; + use crate::services::field::{DateCellContentChangeset, DateCellData, DateFormat, DateTypeOption, TimeFormat}; + use crate::services::row::CellDataOperation; + use flowy_grid_data_model::entities::{FieldMeta, FieldType, TypeOptionDataEntry}; use strum::IntoEnumIterator; #[test] - fn date_description_invalid_input_test() { + fn date_type_option_invalid_input_test() { let type_option = DateTypeOption::default(); let field_type = FieldType::DateTime; let field_meta = FieldBuilder::from_field_type(&field_type).build(); @@ -424,7 +379,7 @@ mod tests { } #[test] - fn date_description_date_format_test() { + fn date_type_option_date_format_test() { let mut type_option = DateTypeOption::default(); let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); for date_format in DateFormat::iter() { @@ -447,7 +402,7 @@ mod tests { } #[test] - fn date_description_time_format_test() { + fn date_type_option_time_format_test() { let mut type_option = DateTypeOption::default(); let field_type = FieldType::DateTime; let field_meta = FieldBuilder::from_field_type(&field_type).build(); @@ -465,7 +420,7 @@ mod tests { }, &field_type, &field_meta, - "May 27,2022 00:00", + "May 27,2022", ); assert_changeset_result( &type_option, @@ -487,9 +442,9 @@ mod tests { }, &field_type, &field_meta, - "May 27,2022 12:00 AM", + "May 27,2022", ); - + // assert_changeset_result( &type_option, DateCellContentChangeset { @@ -517,8 +472,8 @@ mod tests { } #[test] - fn date_description_apply_changeset_test() { - let mut type_option = DateTypeOption::default(); + fn date_type_option_apply_changeset_test() { + let mut type_option = DateTypeOption::new(); let field_type = FieldType::DateTime; let field_meta = FieldBuilder::from_field_type(&field_type).build(); let date_timestamp = "1653609600".to_owned(); @@ -543,7 +498,7 @@ mod tests { }, &field_type, &field_meta, - "May 27,2022 00:00", + "May 27,2022", ); assert_changeset_result( @@ -572,30 +527,53 @@ mod tests { #[test] #[should_panic] - fn date_description_apply_changeset_error_test() { - let mut type_option = DateTypeOption::default(); + fn date_type_option_apply_changeset_error_test() { + let mut type_option = DateTypeOption::new(); type_option.include_time = true; - let _field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); + let field_meta = FieldBuilder::from_field_type(&type_option.field_type()).build(); let date_timestamp = "1653609600".to_owned(); - let changeset = DateCellContentChangeset { - date: Some(date_timestamp.clone()), - time: Some("1:a0".to_owned()), - }; - let _ = type_option.apply_changeset(changeset, None).unwrap(); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(date_timestamp.clone()), + time: Some("1:".to_owned()), + }, + &type_option.field_type(), + &field_meta, + "May 27,2022 01:00", + ); - let changeset = DateCellContentChangeset { - date: Some(date_timestamp), - time: Some("1:".to_owned()), - }; - let _ = type_option.apply_changeset(changeset, None).unwrap(); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(date_timestamp), + time: Some("1:00".to_owned()), + }, + &type_option.field_type(), + &field_meta, + "May 27,2022 01:00", + ); } #[test] #[should_panic] - fn date_description_invalid_data_test() { - let type_option = DateTypeOption::default(); - type_option.apply_changeset("he", None).unwrap(); + fn date_type_option_twelve_hours_to_twenty_four_hours() { + let mut type_option = DateTypeOption::new(); + type_option.include_time = true; + let field_meta = FieldBuilder::from_field_type(&type_option.field_type()).build(); + let date_timestamp = "1653609600".to_owned(); + + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(date_timestamp), + time: Some("1:00 am".to_owned()), + }, + &type_option.field_type(), + &field_meta, + "May 27,2022 01:00", + ); } fn assert_changeset_result( @@ -605,7 +583,7 @@ mod tests { field_meta: &FieldMeta, expected: &str, ) { - let encoded_data = EncodedCellData(Some(type_option.apply_changeset(changeset, None).unwrap())); + let encoded_data = type_option.apply_changeset(changeset, None).unwrap(); assert_eq!( expected.to_owned(), decode_cell_data(encoded_data, type_option, field_meta) @@ -613,24 +591,37 @@ mod tests { } fn assert_decode_timestamp(timestamp: i64, type_option: &DateTypeOption, field_meta: &FieldMeta, expected: &str) { - let serde_json = DateCellDataSerde { timestamp, time: None }.to_string(); + let encoded_data = type_option + .apply_changeset( + DateCellContentChangeset { + date: Some(timestamp.to_string()), + time: None, + }, + None, + ) + .unwrap(); assert_eq!( expected.to_owned(), - decode_cell_data(serde_json, type_option, field_meta) + decode_cell_data(encoded_data, type_option, field_meta) ); } - fn decode_cell_data>>( + fn decode_cell_data>( encoded_data: T, type_option: &DateTypeOption, field_meta: &FieldMeta, ) -> String { - type_option + let decoded_data = type_option .decode_cell_data(encoded_data, &FieldType::DateTime, field_meta) .unwrap() .parse::() - .unwrap() - .date + .unwrap(); + + if type_option.include_time { + format!("{}{}", decoded_data.date, decoded_data.time) + } else { + decoded_data.date + } } } 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/format.rs similarity index 54% rename from frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs rename to frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/format.rs index cf4bd22e3d..149404fa9f 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/format.rs @@ -1,182 +1,16 @@ -use crate::impl_type_option; -use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData}; -use bytes::Bytes; -use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::entities::{ - CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, -}; +use flowy_derive::ProtoBuf_Enum; use lazy_static::lazy_static; -use rust_decimal::Decimal; + use rusty_money::define_currency_set; use serde::{Deserialize, Serialize}; -use std::str::FromStr; use strum::IntoEnumIterator; use strum_macros::EnumIter; lazy_static! { - static ref STRIP_SYMBOL: Vec = make_strip_symbol(); -} - -#[derive(Default)] -pub struct NumberTypeOptionBuilder(NumberTypeOption); -impl_into_box_type_option_builder!(NumberTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(NumberTypeOptionBuilder, NumberTypeOption); - -impl NumberTypeOptionBuilder { - pub fn name(mut self, name: &str) -> Self { - self.0.name = name.to_string(); - self - } - - pub fn set_format(mut self, format: NumberFormat) -> Self { - self.0.set_format(format); - self - } - - pub fn scale(mut self, scale: u32) -> Self { - self.0.scale = scale; - self - } - - pub fn positive(mut self, positive: bool) -> Self { - self.0.sign_positive = positive; - self - } -} - -impl TypeOptionBuilder for NumberTypeOptionBuilder { - fn field_type(&self) -> FieldType { - self.0.field_type() - } - - fn entry(&self) -> &dyn TypeOptionDataEntry { - &self.0 - } -} - -// Number -#[derive(Clone, Debug, Serialize, Deserialize, ProtoBuf)] -pub struct NumberTypeOption { - #[pb(index = 1)] - pub format: NumberFormat, - - #[pb(index = 2)] - pub scale: u32, - - #[pb(index = 3)] - pub symbol: String, - - #[pb(index = 4)] - pub sign_positive: bool, - - #[pb(index = 5)] - pub name: String, -} -impl_type_option!(NumberTypeOption, FieldType::Number); - -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()) - } - 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)) - } - } - } - - fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result - where - C: Into, - { - let changeset = changeset.into(); - let mut data = changeset.trim().to_string(); - - if self.format != NumberFormat::Number { - data = self.strip_symbol(data); - if !data.chars().all(char::is_numeric) { - return Err(FlowyError::invalid_data().context("Should only contain numbers")); - } - } - - Ok(data) - } -} - -impl std::default::Default for NumberTypeOption { - fn default() -> Self { - let format = NumberFormat::default(); - let symbol = format.symbol(); - NumberTypeOption { - format, - scale: 0, - symbol, - sign_positive: true, - name: "Number".to_string(), - } - } -} - -impl NumberTypeOption { - pub fn set_format(&mut self, format: NumberFormat) { - self.format = format; - self.symbol = format.symbol(); - } - - fn money_from_str(&self, s: &str) -> String { - match Decimal::from_str(s) { - Ok(mut decimal) => { - match decimal.set_scale(self.scale) { - Ok(_) => {} - Err(e) => { - tracing::error!("Set decimal scale failed: {:?}", e); - } - } - - decimal.set_sign_positive(self.sign_positive); - let money = rusty_money::Money::from_decimal(decimal, self.format.currency()); - money.to_string() - } - Err(_) => String::new(), - } - } - - fn strip_symbol(&self, s: T) -> String { - let mut s = s.to_string(); - if !s.chars().all(char::is_numeric) { - s.retain(|c| !STRIP_SYMBOL.contains(&c.to_string())); - } - s - } + pub static ref CURRENCY_SYMBOL: Vec = NumberFormat::iter() + .map(|format| format.symbol()) + .collect::>(); + pub static ref STRIP_SYMBOL: Vec = vec![",".to_owned(), ".".to_owned()]; } #[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)] @@ -609,137 +443,3 @@ impl NumberFormat { self.currency().symbol.to_string() } } - -fn make_strip_symbol() -> Vec { - let mut symbols = vec![",".to_owned(), ".".to_owned()]; - for format in NumberFormat::iter() { - symbols.push(format.symbol()); - } - symbols -} - -#[cfg(test)] -mod tests { - use crate::services::field::FieldBuilder; - use crate::services::field::{NumberFormat, NumberTypeOption}; - 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_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_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()); - - for format in NumberFormat::iter() { - type_option.format = format; - match format { - NumberFormat::Number => { - assert_equal(&type_option, "18443", "18443", &field_type, &field_meta); - } - NumberFormat::USD => { - 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_equal(&type_option, "18443", "¥18,443", &field_type, &field_meta); - } - NumberFormat::Yuan => { - assert_equal(&type_option, "18443", "CN¥18,443", &field_type, &field_meta); - } - NumberFormat::EUR => { - assert_equal(&type_option, "18443", "€18.443", &field_type, &field_meta); - } - _ => {} - } - } - } - - #[test] - fn number_description_scale_test() { - let mut type_option = NumberTypeOption { - scale: 1, - ..Default::default() - }; - 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_equal(&type_option, "18443", "18443", &field_type, &field_meta); - } - NumberFormat::USD => { - assert_equal(&type_option, "18443", "$1,844.3", &field_type, &field_meta); - } - NumberFormat::Yen => { - assert_equal(&type_option, "18443", "¥1,844.3", &field_type, &field_meta); - } - NumberFormat::EUR => { - assert_equal(&type_option, "18443", "€1.844,3", &field_type, &field_meta); - } - _ => {} - } - } - } - - #[test] - fn number_description_sign_test() { - let mut type_option = NumberTypeOption { - sign_positive: false, - ..Default::default() - }; - 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_equal(&type_option, "18443", "18443", &field_type, &field_meta); - } - NumberFormat::USD => { - assert_equal(&type_option, "18443", "-$18,443", &field_type, &field_meta); - } - NumberFormat::Yen => { - assert_equal(&type_option, "18443", "-¥18,443", &field_type, &field_meta); - } - NumberFormat::EUR => { - 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/number_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs new file mode 100644 index 0000000000..fffbad97bf --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs @@ -0,0 +1,6 @@ +#![allow(clippy::module_inception)] +mod format; +mod number_type_option; + +pub use format::*; +pub use number_type_option::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs new file mode 100644 index 0000000000..9c830797ed --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs @@ -0,0 +1,346 @@ +use crate::impl_type_option; + +use crate::services::field::type_options::number_type_option::format::*; +use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; +use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData}; +use bytes::Bytes; +use flowy_derive::ProtoBuf; +use flowy_error::{FlowyError, FlowyResult}; +use flowy_grid_data_model::entities::{ + CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, +}; +use rust_decimal::Decimal; + +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +#[derive(Default)] +pub struct NumberTypeOptionBuilder(NumberTypeOption); +impl_into_box_type_option_builder!(NumberTypeOptionBuilder); +impl_builder_from_json_str_and_from_bytes!(NumberTypeOptionBuilder, NumberTypeOption); + +impl NumberTypeOptionBuilder { + pub fn name(mut self, name: &str) -> Self { + self.0.name = name.to_string(); + self + } + + pub fn set_format(mut self, format: NumberFormat) -> Self { + self.0.set_format(format); + self + } + + pub fn scale(mut self, scale: u32) -> Self { + self.0.scale = scale; + self + } + + pub fn positive(mut self, positive: bool) -> Self { + self.0.sign_positive = positive; + self + } +} + +impl TypeOptionBuilder for NumberTypeOptionBuilder { + fn field_type(&self) -> FieldType { + self.0.field_type() + } + + fn entry(&self) -> &dyn TypeOptionDataEntry { + &self.0 + } +} + +// Number +#[derive(Clone, Debug, Serialize, Deserialize, ProtoBuf)] +pub struct NumberTypeOption { + #[pb(index = 1)] + pub format: NumberFormat, + + #[pb(index = 2)] + pub scale: u32, + + #[pb(index = 3)] + pub symbol: String, + + #[pb(index = 4)] + pub sign_positive: bool, + + #[pb(index = 5)] + pub name: String, +} +impl_type_option!(NumberTypeOption, FieldType::Number); + +impl NumberTypeOption { + pub fn new() -> Self { + Self::default() + } + + fn cell_content_from_number_str(&self, s: &str) -> FlowyResult { + match self.format { + NumberFormat::Number => { + if let Ok(v) = s.parse::() { + return Ok(v.to_string()); + } + + if let Ok(v) = s.parse::() { + return Ok(v.to_string()); + } + + Ok("".to_string()) + } + NumberFormat::Percent => { + let content = s.parse::().map_or(String::new(), |v| v.to_string()); + Ok(content) + } + _ => self.money_from_number_str(s), + } + } + + pub fn set_format(&mut self, format: NumberFormat) { + self.format = format; + self.symbol = format.symbol(); + } + + fn money_from_number_str(&self, s: &str) -> FlowyResult { + let mut number = self.strip_currency_symbol(s); + + if s.is_empty() { + return Ok("".to_string()); + } + + match Decimal::from_str(&number) { + Ok(mut decimal) => { + decimal.set_sign_positive(self.sign_positive); + let money = rusty_money::Money::from_decimal(decimal, self.format.currency()).to_string(); + Ok(money) + } + Err(_) => match rusty_money::Money::from_str(&number, self.format.currency()) { + Ok(money) => Ok(money.to_string()), + Err(_) => { + number.retain(|c| !STRIP_SYMBOL.contains(&c.to_string())); + if number.chars().all(char::is_numeric) { + self.money_from_number_str(&number) + } else { + Err(FlowyError::invalid_data().context("Should only contain numbers")) + } + } + }, + } + } + + fn strip_currency_symbol(&self, s: T) -> String { + let mut s = s.to_string(); + for symbol in CURRENCY_SYMBOL.iter() { + if s.starts_with(symbol) { + s = s.strip_prefix(symbol).unwrap_or("").to_string(); + break; + } + } + s + } +} + +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()) + } + NumberFormat::Percent => { + let content = cell_data.parse::().map_or(String::new(), |v| v.to_string()); + Ok(DecodedCellData::new(content)) + } + _ => { + let content = self + .money_from_number_str(&cell_data) + .unwrap_or_else(|_| "".to_string()); + Ok(DecodedCellData::new(content)) + } + } + } + + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { + let changeset = changeset.into(); + let data = changeset.trim().to_string(); + let _ = self.cell_content_from_number_str(&data)?; + Ok(data) + } +} + +impl std::default::Default for NumberTypeOption { + fn default() -> Self { + let format = NumberFormat::default(); + let symbol = format.symbol(); + NumberTypeOption { + format, + scale: 0, + symbol, + sign_positive: true, + name: "Number".to_string(), + } + } +} + +#[cfg(test)] +mod tests { + use crate::services::field::FieldBuilder; + use crate::services::field::{NumberFormat, NumberTypeOption}; + use crate::services::row::CellDataOperation; + use flowy_grid_data_model::entities::{FieldMeta, FieldType}; + use strum::IntoEnumIterator; + + #[test] + fn number_type_option_invalid_input_test() { + let type_option = NumberTypeOption::default(); + 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_type_option_strip_symbol_test() { + let mut type_option = NumberTypeOption::new(); + type_option.format = NumberFormat::USD; + assert_eq!(type_option.strip_currency_symbol("$18,443"), "18,443".to_owned()); + + type_option.format = NumberFormat::Yuan; + assert_eq!(type_option.strip_currency_symbol("$0.2"), "0.2".to_owned()); + } + + #[test] + fn number_type_option_format_number_test() { + let mut type_option = NumberTypeOption::default(); + 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_equal(&type_option, "18443", "18443", &field_type, &field_meta); + } + NumberFormat::USD => { + assert_equal(&type_option, "18443", "$18,443", &field_type, &field_meta); + } + NumberFormat::Yen => { + assert_equal(&type_option, "18443", "¥18,443", &field_type, &field_meta); + } + NumberFormat::Yuan => { + assert_equal(&type_option, "18443", "CN¥18,443", &field_type, &field_meta); + } + NumberFormat::EUR => { + assert_equal(&type_option, "18443", "€18.443", &field_type, &field_meta); + } + _ => {} + } + } + } + + #[test] + fn number_type_option_format_str_test() { + let mut type_option = NumberTypeOption::default(); + 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_equal(&type_option, "18443", "18443", &field_type, &field_meta); + assert_equal(&type_option, "0.2", "0.2", &field_type, &field_meta); + } + NumberFormat::USD => { + assert_equal(&type_option, "$18,44", "$1,844", &field_type, &field_meta); + assert_equal(&type_option, "$0.2", "$0.2", &field_type, &field_meta); + assert_equal(&type_option, "", "", &field_type, &field_meta); + assert_equal(&type_option, "abc", "", &field_type, &field_meta); + } + NumberFormat::Yen => { + assert_equal(&type_option, "¥18,44", "¥1,844", &field_type, &field_meta); + assert_equal(&type_option, "¥1844", "¥1,844", &field_type, &field_meta); + } + NumberFormat::Yuan => { + assert_equal(&type_option, "CN¥18,44", "CN¥1,844", &field_type, &field_meta); + assert_equal(&type_option, "CN¥1844", "CN¥1,844", &field_type, &field_meta); + } + NumberFormat::EUR => { + assert_equal(&type_option, "€18.44", "€18,44", &field_type, &field_meta); + assert_equal(&type_option, "€0.5", "€0,5", &field_type, &field_meta); + assert_equal(&type_option, "€1844", "€1.844", &field_type, &field_meta); + } + _ => {} + } + } + } + + #[test] + fn number_description_sign_test() { + let mut type_option = NumberTypeOption { + sign_positive: false, + ..Default::default() + }; + 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_equal(&type_option, "18443", "18443", &field_type, &field_meta); + } + NumberFormat::USD => { + assert_equal(&type_option, "18443", "-$18,443", &field_type, &field_meta); + } + NumberFormat::Yen => { + assert_equal(&type_option, "18443", "-¥18,443", &field_type, &field_meta); + } + NumberFormat::EUR => { + 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 81a7ff5c04..a7ad30cc31 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 @@ -95,7 +95,7 @@ impl SelectOptionOperation for SingleSelectTypeOption { } } -impl CellDataOperation for SingleSelectTypeOption { +impl CellDataOperation for SingleSelectTypeOption { fn decode_cell_data( &self, encoded_data: T, @@ -193,7 +193,7 @@ impl SelectOptionOperation for MultiSelectTypeOption { } } -impl CellDataOperation for MultiSelectTypeOption { +impl CellDataOperation for MultiSelectTypeOption { fn decode_cell_data( &self, encoded_data: T, 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 3acdfd97c5..b59681a2ea 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 @@ -27,11 +27,11 @@ impl TypeOptionBuilder for RichTextTypeOptionBuilder { #[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)] pub struct RichTextTypeOption { #[pb(index = 1)] - data: String, //It's not used. + data: String, //It's not used yet } impl_type_option!(RichTextTypeOption, FieldType::RichText); -impl CellDataOperation for RichTextTypeOption { +impl CellDataOperation for RichTextTypeOption { fn decode_cell_data( &self, encoded_data: T, @@ -80,10 +80,10 @@ mod tests { // date 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(); + assert_eq!( type_option - .decode_cell_data(json, &field_type, &date_time_field_meta) + .decode_cell_data(1647251762.to_string(), &field_type, &date_time_field_meta) .unwrap() .parse::() .unwrap() 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 index 7299b1babd..ecb2a8e16f 100644 --- 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 @@ -30,11 +30,11 @@ impl TypeOptionBuilder for URLTypeOptionBuilder { #[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] pub struct URLTypeOption { #[pb(index = 1)] - data: String, //It's not used. + data: String, //It's not used yet. } impl_type_option!(URLTypeOption, FieldType::URL); -impl CellDataOperation, String> for URLTypeOption { +impl CellDataOperation> for URLTypeOption { fn decode_cell_data( &self, encoded_data: T, @@ -56,28 +56,31 @@ impl CellDataOperation, String> for URLTypeOption { C: Into, { let changeset = changeset.into(); - let mut cell_data = URLCellData { - url: "".to_string(), - content: changeset.to_string(), - }; - + let mut url = "".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()); - } + url = auto_append_scheme(m.as_str()); + } + URLCellData { + url, + content: changeset.to_string(), + } + .to_json() + } +} + +fn auto_append_scheme(s: &str) -> String { + // Only support https scheme by now + match url::Url::parse(s) { + Ok(url) => { + if url.scheme() == "https" { + url.into() + } else { + format!("https://{}", s) } } - - cell_data.to_json() + Err(_) => { + format!("https://{}", s) + } } } 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 e4cee15a6e..82db6aac1b 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -487,6 +487,35 @@ impl GridMetaEditor { self.grid_pad.read().await.delta_bytes() } + pub async fn duplicate_grid(&self) -> FlowyResult { + let grid_pad = self.grid_pad.read().await; + let original_blocks = grid_pad.get_block_metas(); + let (duplicated_fields, duplicated_blocks) = grid_pad.duplicate_grid_meta().await; + + let mut blocks_meta_data = vec![]; + if original_blocks.len() == duplicated_blocks.len() { + for (index, original_block_meta) in original_blocks.iter().enumerate() { + let grid_block_meta_editor = self.block_manager.get_editor(&original_block_meta.block_id).await?; + let duplicated_block_id = &duplicated_blocks[index].block_id; + + tracing::trace!("Duplicate block:{} meta data", duplicated_block_id); + let duplicated_block_meta_data = grid_block_meta_editor + .duplicate_block_meta_data(duplicated_block_id) + .await; + blocks_meta_data.push(duplicated_block_meta_data); + } + } else { + debug_assert_eq!(original_blocks.len(), duplicated_blocks.len()); + } + drop(grid_pad); + + Ok(BuildGridContext { + field_metas: duplicated_fields, + blocks: duplicated_blocks, + blocks_meta_data, + }) + } + async fn modify(&self, f: F) -> FlowyResult<()> where F: for<'a> FnOnce(&'a mut GridMetaPad) -> FlowyResult>, 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 d93f84244a..7423bf0fcd 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 @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use std::fmt::Formatter; use std::str::FromStr; -pub trait CellDataOperation { +pub trait CellDataOperation { fn decode_cell_data( &self, encoded_data: T, @@ -14,14 +14,14 @@ pub trait CellDataOperation { field_meta: &FieldMeta, ) -> FlowyResult where - T: Into; + T: Into; // fn apply_changeset>( &self, changeset: C, cell_meta: Option, - ) -> FlowyResult; + ) -> FlowyResult; } #[derive(Debug)] @@ -128,9 +128,7 @@ pub fn apply_cell_data_changeset>( 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) - .map(|data| data.to_string()), + FieldType::DateTime => DateTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), 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), diff --git a/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs b/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs index 41835804af..846dddd55e 100644 --- a/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs +++ b/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs @@ -173,7 +173,7 @@ impl ViewDataProcessor for TextBlockViewDataProcessor { }) } - fn delta_bytes(&self, view_id: &str) -> FutureResult { + fn view_delta_data(&self, view_id: &str) -> FutureResult { let view_id = view_id.to_string(); let manager = self.0.clone(); FutureResult::new(async move { @@ -197,7 +197,7 @@ impl ViewDataProcessor for TextBlockViewDataProcessor { }) } - fn process_create_view_data( + fn process_view_delta_data( &self, _user_id: &str, _view_id: &str, @@ -245,13 +245,13 @@ impl ViewDataProcessor for GridViewDataProcessor { }) } - fn delta_bytes(&self, view_id: &str) -> FutureResult { + fn view_delta_data(&self, view_id: &str) -> FutureResult { let view_id = view_id.to_string(); let grid_manager = self.0.clone(); FutureResult::new(async move { let editor = grid_manager.open_grid(view_id).await?; - let delta_bytes = editor.delta_bytes().await; - Ok(delta_bytes) + let delta_bytes = editor.duplicate_grid().await?; + Ok(delta_bytes.into()) }) } @@ -264,7 +264,7 @@ impl ViewDataProcessor for GridViewDataProcessor { FutureResult::new(async move { make_grid_view_data(&user_id, &view_id, grid_manager, build_context).await }) } - fn process_create_view_data(&self, user_id: &str, view_id: &str, data: Vec) -> FutureResult { + fn process_view_delta_data(&self, user_id: &str, view_id: &str, data: Vec) -> FutureResult { let user_id = user_id.to_string(); let view_id = view_id.to_string(); let grid_manager = self.0.clone(); diff --git a/frontend/rust-lib/flowy-text-block/src/event_map.rs b/frontend/rust-lib/flowy-text-block/src/event_map.rs index a355af8bc3..f995fd282b 100644 --- a/frontend/rust-lib/flowy-text-block/src/event_map.rs +++ b/frontend/rust-lib/flowy-text-block/src/event_map.rs @@ -9,16 +9,16 @@ pub fn create(block_manager: Arc) -> Module { let mut module = Module::new().name(env!("CARGO_PKG_NAME")).data(block_manager); module = module - .event(BlockEvent::GetBlockData, get_block_data_handler) - .event(BlockEvent::ApplyDelta, apply_delta_handler) - .event(BlockEvent::ExportDocument, export_handler); + .event(TextBlockEvent::GetBlockData, get_block_data_handler) + .event(TextBlockEvent::ApplyDelta, apply_delta_handler) + .event(TextBlockEvent::ExportDocument, export_handler); module } #[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)] #[event_err = "FlowyError"] -pub enum BlockEvent { +pub enum TextBlockEvent { #[event(input = "TextBlockId", output = "TextBlockDelta")] GetBlockData = 0, diff --git a/frontend/rust-lib/flowy-text-block/src/protobuf/model/event_map.rs b/frontend/rust-lib/flowy-text-block/src/protobuf/model/event_map.rs index ab1a5da855..f0356559c5 100644 --- a/frontend/rust-lib/flowy-text-block/src/protobuf/model/event_map.rs +++ b/frontend/rust-lib/flowy-text-block/src/protobuf/model/event_map.rs @@ -24,31 +24,31 @@ // const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2; #[derive(Clone,PartialEq,Eq,Debug,Hash)] -pub enum BlockEvent { +pub enum TextBlockEvent { GetBlockData = 0, ApplyDelta = 1, ExportDocument = 2, } -impl ::protobuf::ProtobufEnum for BlockEvent { +impl ::protobuf::ProtobufEnum for TextBlockEvent { fn value(&self) -> i32 { *self as i32 } - fn from_i32(value: i32) -> ::std::option::Option { + fn from_i32(value: i32) -> ::std::option::Option { match value { - 0 => ::std::option::Option::Some(BlockEvent::GetBlockData), - 1 => ::std::option::Option::Some(BlockEvent::ApplyDelta), - 2 => ::std::option::Option::Some(BlockEvent::ExportDocument), + 0 => ::std::option::Option::Some(TextBlockEvent::GetBlockData), + 1 => ::std::option::Option::Some(TextBlockEvent::ApplyDelta), + 2 => ::std::option::Option::Some(TextBlockEvent::ExportDocument), _ => ::std::option::Option::None } } fn values() -> &'static [Self] { - static values: &'static [BlockEvent] = &[ - BlockEvent::GetBlockData, - BlockEvent::ApplyDelta, - BlockEvent::ExportDocument, + static values: &'static [TextBlockEvent] = &[ + TextBlockEvent::GetBlockData, + TextBlockEvent::ApplyDelta, + TextBlockEvent::ExportDocument, ]; values } @@ -56,30 +56,30 @@ impl ::protobuf::ProtobufEnum for BlockEvent { fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor { static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT; descriptor.get(|| { - ::protobuf::reflect::EnumDescriptor::new_pb_name::("BlockEvent", file_descriptor_proto()) + ::protobuf::reflect::EnumDescriptor::new_pb_name::("TextBlockEvent", file_descriptor_proto()) }) } } -impl ::std::marker::Copy for BlockEvent { +impl ::std::marker::Copy for TextBlockEvent { } -impl ::std::default::Default for BlockEvent { +impl ::std::default::Default for TextBlockEvent { fn default() -> Self { - BlockEvent::GetBlockData + TextBlockEvent::GetBlockData } } -impl ::protobuf::reflect::ProtobufValue for BlockEvent { +impl ::protobuf::reflect::ProtobufValue for TextBlockEvent { fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef { ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self)) } } static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x0fevent_map.proto*B\n\nBlockEvent\x12\x10\n\x0cGetBlockData\x10\0\ - \x12\x0e\n\nApplyDelta\x10\x01\x12\x12\n\x0eExportDocument\x10\x02b\x06p\ - roto3\ + \n\x0fevent_map.proto*F\n\x0eTextBlockEvent\x12\x10\n\x0cGetBlockData\ + \x10\0\x12\x0e\n\nApplyDelta\x10\x01\x12\x12\n\x0eExportDocument\x10\x02\ + b\x06proto3\ "; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; diff --git a/frontend/rust-lib/flowy-text-block/src/protobuf/proto/event_map.proto b/frontend/rust-lib/flowy-text-block/src/protobuf/proto/event_map.proto index 3ebf0755d3..f7e088c938 100644 --- a/frontend/rust-lib/flowy-text-block/src/protobuf/proto/event_map.proto +++ b/frontend/rust-lib/flowy-text-block/src/protobuf/proto/event_map.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -enum BlockEvent { +enum TextBlockEvent { GetBlockData = 0; ApplyDelta = 1; ExportDocument = 2; diff --git a/frontend/scripts/build_sdk.sh b/frontend/scripts/build_sdk.sh index 061fa58d0a..330bd22738 100755 --- a/frontend/scripts/build_sdk.sh +++ b/frontend/scripts/build_sdk.sh @@ -25,7 +25,7 @@ Linux-x86) ;; macOS) - cargo make --profile development-mac flowy-sdk-dev + cargo make --profile "development-mac-$(uname -m)" flowy-sdk-dev ;; Windows) diff --git a/shared-lib/flowy-grid-data-model/src/entities/meta.rs b/shared-lib/flowy-grid-data-model/src/entities/meta.rs index 328b368fbc..d62965245b 100644 --- a/shared-lib/flowy-grid-data-model/src/entities/meta.rs +++ b/shared-lib/flowy-grid-data-model/src/entities/meta.rs @@ -203,11 +203,17 @@ impl CellMeta { } } -#[derive(Clone, Deserialize, Serialize)] +#[derive(Clone, Default, Deserialize, Serialize)] pub struct BuildGridContext { pub field_metas: Vec, - pub block_meta: GridBlockMeta, - pub block_meta_data: GridBlockMetaData, + pub blocks: Vec, + pub blocks_meta_data: Vec, +} + +impl BuildGridContext { + pub fn new() -> Self { + Self::default() + } } impl std::convert::From for Bytes { @@ -225,19 +231,3 @@ impl std::convert::TryFrom for BuildGridContext { Ok(ctx) } } - -impl std::default::Default for BuildGridContext { - fn default() -> Self { - let block_meta = GridBlockMeta::new(); - let block_meta_data = GridBlockMetaData { - block_id: block_meta.block_id.clone(), - rows: vec![], - }; - - Self { - field_metas: vec![], - block_meta, - block_meta_data, - } - } -} 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 3370719e68..175890ad01 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 @@ -1,7 +1,9 @@ use crate::entities::revision::{md5, RepeatedRevision, Revision}; use crate::errors::{CollaborateError, CollaborateResult}; use crate::util::{cal_diff, make_delta_from_revisions}; -use flowy_grid_data_model::entities::{gen_block_id, CellMeta, GridBlockMetaData, RowMeta, RowMetaChangeset}; +use flowy_grid_data_model::entities::{ + gen_block_id, gen_row_id, CellMeta, GridBlockMetaData, RowMeta, RowMetaChangeset, +}; use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder}; use serde::{Deserialize, Serialize}; use std::borrow::Cow; @@ -22,6 +24,23 @@ pub struct GridBlockMetaPad { } impl GridBlockMetaPad { + pub async fn duplicate_data(&self, duplicated_block_id: &str) -> GridBlockMetaData { + let duplicated_rows = self + .rows + .iter() + .map(|row| { + let mut duplicated_row = row.as_ref().clone(); + duplicated_row.id = gen_row_id(); + duplicated_row.block_id = duplicated_block_id.to_string(); + duplicated_row + }) + .collect::>(); + GridBlockMetaData { + block_id: duplicated_block_id.to_string(), + rows: duplicated_rows, + } + } + pub fn from_delta(delta: GridBlockMetaDelta) -> CollaborateResult { let s = delta.to_str()?; let meta_data: GridBlockMetaData = serde_json::from_str(&s).map_err(|e| { diff --git a/shared-lib/flowy-sync/src/client_grid/grid_builder.rs b/shared-lib/flowy-sync/src/client_grid/grid_builder.rs index fb800a9626..95e707a287 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_builder.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_builder.rs @@ -1,11 +1,27 @@ use crate::errors::{CollaborateError, CollaborateResult}; -use flowy_grid_data_model::entities::{BuildGridContext, FieldMeta, RowMeta}; +use flowy_grid_data_model::entities::{BuildGridContext, FieldMeta, GridBlockMeta, GridBlockMetaData, RowMeta}; -#[derive(Default)] pub struct GridBuilder { build_context: BuildGridContext, } +impl std::default::Default for GridBuilder { + fn default() -> Self { + let mut build_context = BuildGridContext::new(); + + let block_meta = GridBlockMeta::new(); + let block_meta_data = GridBlockMetaData { + block_id: block_meta.block_id.clone(), + rows: vec![], + }; + + build_context.blocks.push(block_meta); + build_context.blocks_meta_data.push(block_meta_data); + + GridBuilder { build_context } + } +} + impl GridBuilder { pub fn add_field(mut self, field: FieldMeta) -> Self { self.build_context.field_metas.push(field); @@ -13,9 +29,11 @@ impl GridBuilder { } pub fn add_empty_row(mut self) -> Self { - let row = RowMeta::new(&self.build_context.block_meta.block_id); - self.build_context.block_meta_data.rows.push(row); - self.build_context.block_meta.row_count += 1; + let row = RowMeta::new(&self.build_context.blocks.first().unwrap().block_id); + let block_meta = self.build_context.blocks.first_mut().unwrap(); + let block_meta_data = self.build_context.blocks_meta_data.first_mut().unwrap(); + block_meta_data.rows.push(row); + block_meta.row_count += 1; self } @@ -57,13 +75,13 @@ mod tests { let grid_meta = GridMeta { grid_id, fields: build_context.field_metas, - blocks: vec![build_context.block_meta], + blocks: build_context.blocks, }; let grid_meta_delta = make_grid_delta(&grid_meta); let _: GridMeta = serde_json::from_str(&grid_meta_delta.to_str().unwrap()).unwrap(); - let grid_block_meta_delta = make_block_meta_delta(&build_context.block_meta_data); + let grid_block_meta_delta = make_block_meta_delta(build_context.blocks_meta_data.first().unwrap()); let _: GridBlockMetaData = serde_json::from_str(&grid_block_meta_delta.to_str().unwrap()).unwrap(); } } diff --git a/shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs index 791648cf14..0e255f0f12 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs @@ -3,8 +3,8 @@ use crate::errors::{internal_error, CollaborateError, CollaborateResult}; use crate::util::{cal_diff, make_delta_from_revisions}; use bytes::Bytes; use flowy_grid_data_model::entities::{ - gen_grid_id, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, GridBlockMeta, GridBlockMetaChangeset, - GridMeta, + gen_block_id, gen_grid_id, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, GridBlockMeta, + GridBlockMetaChangeset, GridMeta, }; use lib_infra::util::move_vec_element; use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder}; @@ -24,6 +24,28 @@ pub trait JsonDeserializer { } impl GridMetaPad { + pub async fn duplicate_grid_meta(&self) -> (Vec, Vec) { + let fields = self + .grid_meta + .fields + .iter() + .map(|field| field.clone()) + .collect::>(); + + let blocks = self + .grid_meta + .blocks + .iter() + .map(|block| { + let mut duplicated_block = block.clone(); + duplicated_block.block_id = gen_block_id(); + duplicated_block + }) + .collect::>(); + + (fields, blocks) + } + pub fn from_delta(delta: GridMetaDelta) -> CollaborateResult { let s = delta.to_str()?; let grid: GridMeta = serde_json::from_str(&s)