diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 40f51c8132..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: @@ -34,6 +34,7 @@ jobs: with: channel: 'stable' cache: true + flutter-version: '3.0.0' - name: Cache Cargo uses: actions/cache@v2 diff --git a/.github/workflows/dart_lint.yml b/.github/workflows/dart_lint.yml index 81460cec1c..ca0536906e 100644 --- a/.github/workflows/dart_lint.yml +++ b/.github/workflows/dart_lint.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v2 - uses: subosito/flutter-action@v1 with: - flutter-version: '2.10.0' + flutter-version: '3.0.0' channel: "stable" - name: Deps Flutter run: flutter packages pub get diff --git a/.github/workflows/dart_test.yml b/.github/workflows/dart_test.yml index db479d2905..9938f02091 100644 --- a/.github/workflows/dart_test.yml +++ b/.github/workflows/dart_test.yml @@ -25,6 +25,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: 'stable' + flutter-version: '3.0.0' cache: true - name: Cache Cargo diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 93dedaa43d..a2c9c7b133 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,6 +50,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: 'stable' + flutter-version: '3.0.0' - name: Pre build working-directory: frontend @@ -98,6 +99,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: 'stable' + flutter-version: '3.0.0' - name: Pre build working-directory: frontend @@ -111,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/.vscode/launch.json b/frontend/.vscode/launch.json index 66fc02b1a1..4328389d39 100644 --- a/frontend/.vscode/launch.json +++ b/frontend/.vscode/launch.json @@ -5,40 +5,60 @@ "version": "0.2.0", "configurations": [ { - "name": "app_flowy", + // This task builds the Rust and Dart code of AppFlowy. + "name": "AF: Build All", "request": "launch", "program": "./lib/main.dart", "type": "dart", - "preLaunchTask": "build_flowy_sdk", + "preLaunchTask": "AF: build_flowy_sdk", "env":{ "RUST_LOG":"info" }, "cwd": "${workspaceRoot}/app_flowy" }, { - "name": "app_flowy(trace)", + // This task only builds the Dart code of AppFlowy. + "name": "AF: Build Dart Only", + "request": "launch", + "program": "${workspaceRoot}/lib/main.dart", + "type": "dart", + "env": { + "RUST_LOG": "debug" + }, + "cwd": "${workspaceRoot}" + }, + { + // This task builds will: + // - call the clean task, + // - rebuild all the generated Files (including freeze and language files) + // - rebuild the the Rust and Dart code of AppFlowy. + "name": "AF: Clean + Rebuild All", "request": "launch", "program": "./lib/main.dart", "type": "dart", - "preLaunchTask": "build_flowy_sdk", + "preLaunchTask": "AF: Clean + Rebuild All", + "env":{ + "RUST_LOG":"info" + }, + "cwd": "${workspaceRoot}/app_flowy" + }, + + { + "name": "AF: Build All (rustlog: trace)", + "request": "launch", + "program": "./lib/main.dart", + "type": "dart", + "preLaunchTask": "AF: build_flowy_sdk", "env":{ "RUST_LOG":"trace" }, "cwd": "${workspaceRoot}/app_flowy" }, { - "name": "app_flowy (profile mode)", + "name": "AF: app_flowy (profile mode)", "request": "launch", "type": "dart", "flutterMode": "profile" }, - { - "name": "Generate Language Files", - "request": "launch", - "program": "./lib/main.dart", - "type": "dart", - "preLaunchTask": "Generate Language Files", - "cwd": "${workspaceRoot}/app_flowy/" - }, ] -} \ No newline at end of file +} diff --git a/frontend/.vscode/tasks.json b/frontend/.vscode/tasks.json index e902e3dfd5..aec5955c19 100644 --- a/frontend/.vscode/tasks.json +++ b/frontend/.vscode/tasks.json @@ -10,13 +10,35 @@ // ${cwd}: the current working directory of the spawned process "tasks": [ { - "label": "build_flowy_sdk", + "label": "AF: Clean + Rebuild All", + "type": "shell", + "dependsOrder": "sequence", + "dependsOn": [ + "AF: Clean", + "AF: Flutter Pub", + "AF: Flutter Package Get", + "AF: Generate Language Files", + "AF: Generate Freezed Files", + "AF: build_flowy_sdk" + ], + "group": { + "kind": "build", + "isDefault": true, + }, + "presentation": { + "reveal": "always", + "panel": "new" + } + + }, + { + "label": "AF: build_flowy_sdk", "type": "shell", "command": "sh ./scripts/build_sdk.sh", "windows": { "options": { "env": { - "FLOWY_DEV_ENV": "Windows", + "FLOWY_DEV_ENV": "Windows" }, "shell": { "executable": "cmd.exe", @@ -31,27 +53,67 @@ "linux": { "options": { "env": { - "FLOWY_DEV_ENV": "Linux-x86", + "FLOWY_DEV_ENV": "Linux-x86" } - }, + } }, "osx": { "options": { "env": { - "FLOWY_DEV_ENV": "macOS", + "FLOWY_DEV_ENV": "macOS" } - }, + } }, "group": "build", "options": { "cwd": "${workspaceFolder}" - }, - // "problemMatcher": [ - // "$rustc" - // ], + } }, { - "label": "Generate Language Files", + "label": "AF: Code Gen", + "type": "shell", + "dependsOrder": "sequence", + "dependsOn": [ + "AF: Flutter Pub", + "AF: Flutter Package Get", + "AF: Generate Language Files", + "AF: Generate Freezed Files" + ], + "group": { + "kind": "build", + "isDefault": true, + }, + "presentation": { + "reveal": "always", + "panel": "new" + } + }, + { + "label": "AF: Flutter Pub", + "type": "shell", + "command": "flutter pub get", + "options": { + "cwd": "${workspaceFolder}/app_flowy" + } + }, + { + "label": "AF: Flutter Package Get", + "type": "shell", + "command": "flutter packages pub get", + "options": { + "cwd": "${workspaceFolder}/app_flowy" + } + }, + { + "label": "AF: Generate Freezed Files", + "type": "shell", + "command": "flutter pub run build_runner build --delete-conflicting-outputs", + "options": { + "cwd": "${workspaceFolder}/app_flowy" + } + }, + { + "label": "AF: Generate Language Files", "type": "shell", "command": "sh ./scripts/generate_language_files.sh", "windows": { @@ -69,10 +131,10 @@ "group": "build", "options": { "cwd": "${workspaceFolder}" - }, + } }, { - "label": "Clean", + "label": "AF: Clean", "type": "shell", "command": "sh ./scripts/clean.sh", "windows": { @@ -90,7 +152,19 @@ "group": "build", "options": { "cwd": "${workspaceFolder}" - }, + } + }, + { + "label": "AF: flutter build aar", + "type": "flutter", + "command": "flutter", + "args": [ + "build", + "aar" + ], + "group": "build", + "problemMatcher": [], + "detail": "app_flowy" } ] -} \ No newline at end of file +} diff --git a/frontend/Makefile.toml b/frontend/Makefile.toml index 7c82e17ba7..42708d7c32 100644 --- a/frontend/Makefile.toml +++ b/frontend/Makefile.toml @@ -7,6 +7,7 @@ extend = [ { path = "scripts/makefile/docker.toml" }, { path = "scripts/makefile/env.toml" }, { path = "scripts/makefile/flutter.toml" }, + { path = "scripts/makefile/tool.toml" }, ] [config] @@ -44,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" @@ -52,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" @@ -137,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/.vscode/launch.json b/frontend/app_flowy/.vscode/launch.json deleted file mode 100644 index b271a1e6a1..0000000000 --- a/frontend/app_flowy/.vscode/launch.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "app_flowy", - "request": "launch", - "program": "${workspaceRoot}/lib/main.dart", - "type": "dart", - "preLaunchTask": "build_flowy_sdk", - "env": { - "RUST_LOG": "debug" - }, - "cwd": "${workspaceRoot}" - }, - { - "name": "app_flowy(trace)", - "request": "launch", - "program": "${workspaceRoot}/lib/main.dart", - "type": "dart", - "preLaunchTask": "build_flowy_sdk", - "env": { - "RUST_LOG": "trace" - }, - "cwd": "${workspaceRoot}" - }, - { - "name": "app_flowy (profile mode)", - "request": "launch", - "type": "dart", - "flutterMode": "profile" - }, - ] -} \ No newline at end of file diff --git a/frontend/app_flowy/.vscode/settings.json b/frontend/app_flowy/.vscode/settings.json deleted file mode 100644 index 13845cc225..0000000000 --- a/frontend/app_flowy/.vscode/settings.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "[dart]": { - "editor.formatOnSave": true, - "editor.formatOnType": true, - "editor.rulers": [ - 120 - ], - "editor.selectionHighlight": false, - "editor.suggest.snippetsPreventQuickSuggestions": false, - "editor.suggestSelection": "first", - "editor.tabCompletion": "onlySnippets", - "editor.wordBasedSuggestions": false - }, - "svgviewer.enableautopreview": true, - "svgviewer.previewcolumn": "Active", - "svgviewer.showzoominout": true, - "editor.wordWrapColumn": 120, - "editor.minimap.maxColumn": 140, - "prettier.printWidth": 140, - "editor.wordWrap": "wordWrapColumn", - "dart.lineLength": 120, - "files.associations": { - "*.log.*": "log" - }, - "editor.formatOnSave": true, -} \ No newline at end of file diff --git a/frontend/app_flowy/.vscode/tasks.json b/frontend/app_flowy/.vscode/tasks.json deleted file mode 100644 index f46aac1085..0000000000 --- a/frontend/app_flowy/.vscode/tasks.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "version": "2.0.0", - // https://code.visualstudio.com/docs/editor/tasks - // https://gist.github.com/deadalusai/9e13e36d61ec7fb72148 - // ${workspaceRoot}: the root folder of the team - // ${file}: the current opened file - // ${fileBasename}: the current opened file's basename - // ${fileDirname}: the current opened file's dirname - // ${fileExtname}: the current opened file's extension - // ${cwd}: the current working directory of the spawned process - "tasks": [ - { - "label": "build_flowy_sdk", - "type": "shell", - "command": "sh ./scripts/build_sdk.sh", - "windows": { - "options": { - "env": { - "FLOWY_DEV_ENV": "Windows", - }, - "shell": { - "executable": "cmd.exe", - "args": [ - "/d", - "/c", - ".\\scripts\\build_sdk.cmd" - ] - } - } - }, - "linux": { - "options": { - "env": { - "FLOWY_DEV_ENV": "Linux-x86", - } - }, - }, - "osx": { - "options": { - "env": { - "FLOWY_DEV_ENV": "macOS", - } - }, - }, - "group": "build", - "options": { - "cwd": "${workspaceFolder}/../" - }, - // "problemMatcher": [ - // "$rustc" - // ], - }, - { - "label": "Code Gen", - "type": "shell", - "dependsOn": [ - "Flutter Pub", - "Flutter Package Get", - "Generate Language Files", - "Generate Freezed Files" - ], - "group": { - "kind": "build", - "isDefault": true, - }, - "dependsOrder": "sequence", - "presentation": { - "reveal": "always", - "panel": "new" - }, - }, - { - "label": "Flutter Pub", - "type": "shell", - "command": "flutter pub get", - }, - { - "label": "Flutter Package Get", - "type": "shell", - "command": "flutter packages pub get", - }, - { - "label": "Generate Freezed Files", - "type": "shell", - "command": "flutter pub run build_runner build --delete-conflicting-outputs", - }, - { - "label": "Generate Language Files", - "type": "shell", - "command": "sh ./scripts/generate_language_files.sh", - "windows": { - "options": { - "shell": { - "executable": "cmd.exe", - "args": [ - "/d", - "/c", - ".\\scripts\\generate_language_files.cmd" - ] - } - } - }, - "group": "build", - "options": { - "cwd": "${workspaceFolder}/../" - }, - }, - { - "label": "Clean", - "type": "shell", - "command": "sh ./scripts/clean.sh", - "windows": { - "options": { - "shell": { - "executable": "cmd.exe", - "args": [ - "/d", - "/c", - ".\\scripts\\clean.cmd" - ] - } - } - }, - "options": { - "cwd": "${workspaceFolder}/../" - }, - } - ] -} \ No newline at end of file diff --git a/frontend/app_flowy/assets/images/grid/field/url.svg b/frontend/app_flowy/assets/images/grid/field/url.svg new file mode 100644 index 0000000000..f00f5c7aa2 --- /dev/null +++ b/frontend/app_flowy/assets/images/grid/field/url.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index cb081c4ab8..521321d54f 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -96,6 +96,12 @@ "lightMode": "Switch to Light mode", "darkMode": "Switch to Dark mode" }, + "notifications": { + "export": { + "markdown": "Exported Note To Markdown", + "path": "Documents/flowy" + } + }, "contactsPage": { "title": "Contacts", "whatsHappening": "What's happening this week?", @@ -160,6 +166,7 @@ "numberFieldName": "Numbers", "singleSelectFieldName": "Select", "multiSelectFieldName": "Multiselect", + "urlFieldName": "URL", "numberFormat": " Number format", "dateFormat": " Date format", "includeTime": " Include time", @@ -168,6 +175,7 @@ "dateFormatLocal": "Month/Month/Day", "dateFormatUS": "Month/Month/Day", "timeFormat": " Time format", + "invalidTimeFormat": "Invalid format", "timeFormatTwelveHour": "12 hour", "timeFormatTwentyFourHour": "24 hour", "addSelectOption": "Add an option", @@ -178,7 +186,8 @@ "row": { "duplicate": "Duplicate", "delete": "Delete", - "textPlaceholder": "Empty" + "textPlaceholder": "Empty", + "copyProperty": "Copied property to clipboard" }, "selectOption": { "create": "Create", @@ -200,5 +209,9 @@ }, "document":{ "menuName":"Doc" + "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/core/frameless_window.dart b/frontend/app_flowy/lib/core/frameless_window.dart new file mode 100644 index 0000000000..a7d6417cd3 --- /dev/null +++ b/frontend/app_flowy/lib/core/frameless_window.dart @@ -0,0 +1,67 @@ +import 'package:flutter/services.dart'; +import 'package:flutter/material.dart'; +import 'dart:io' show Platform; + +class CocoaWindowChannel { + CocoaWindowChannel._(); + + final MethodChannel _channel = const MethodChannel("flutter/cocoaWindow"); + + static final CocoaWindowChannel instance = CocoaWindowChannel._(); + + Future setWindowPosition(Offset offset) async { + await _channel.invokeMethod("setWindowPosition", [offset.dx, offset.dy]); + } + + Future> getWindowPosition() async { + final raw = await _channel.invokeMethod("getWindowPosition"); + final arr = raw as List; + final List result = arr.map((s) => s as double).toList(); + return result; + } + + Future zoom() async { + await _channel.invokeMethod("zoom"); + } +} + +class MoveWindowDetector extends StatefulWidget { + const MoveWindowDetector({Key? key, this.child}) : super(key: key); + + final Widget? child; + + @override + _MoveWindowDetectorState createState() => _MoveWindowDetectorState(); +} + +class _MoveWindowDetectorState extends State { + double winX = 0; + double winY = 0; + + @override + Widget build(BuildContext context) { + if (!Platform.isMacOS) { + return widget.child ?? Container(); + } + return GestureDetector( + // https://stackoverflow.com/questions/52965799/flutter-gesturedetector-not-working-with-containers-in-stack + behavior: HitTestBehavior.translucent, + onDoubleTap: () async { + await CocoaWindowChannel.instance.zoom(); + }, + onPanStart: (DragStartDetails details) { + winX = details.globalPosition.dx; + winY = details.globalPosition.dy; + }, + onPanUpdate: (DragUpdateDetails details) async { + final windowPos = await CocoaWindowChannel.instance.getWindowPosition(); + final double dx = windowPos[0]; + final double dy = windowPos[1]; + final deltaX = details.globalPosition.dx - winX; + final deltaY = details.globalPosition.dy - winY; + await CocoaWindowChannel.instance.setWindowPosition(Offset(dx + deltaX, dy - deltaY)); + }, + child: widget.child, + ); + } +} diff --git a/frontend/app_flowy/lib/startup/deps_resolver.dart b/frontend/app_flowy/lib/startup/deps_resolver.dart index 8bc5d4dd41..b378d6ee7b 100644 --- a/frontend/app_flowy/lib/startup/deps_resolver.dart +++ b/frontend/app_flowy/lib/startup/deps_resolver.dart @@ -15,10 +15,8 @@ import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; 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-grid-data-model/grid.pb.dart' show EditFieldContext; -import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.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 { @@ -49,6 +47,8 @@ void _resolveUserDeps(GetIt getIt) { } void _resolveHomeDeps(GetIt getIt) { + getIt.registerSingleton(FToast()); + getIt.registerSingleton(MenuSharedState()); getIt.registerFactoryParam( @@ -157,21 +157,14 @@ void _resolveGridDeps(GetIt getIt) { ), ); - getIt.registerFactoryParam( - (gridId, fieldLoader) => FieldEditorBloc( - gridId: gridId, - fieldLoader: fieldLoader, - ), - ); - getIt.registerFactoryParam( (context, _) => TextCellBloc( cellContext: context, ), ); - getIt.registerFactoryParam( - (context, _) => SelectionCellBloc( + getIt.registerFactoryParam( + (context, _) => SelectOptionCellBloc( cellContext: context, ), ); @@ -195,18 +188,6 @@ void _resolveGridDeps(GetIt getIt) { ), ); - getIt.registerFactoryParam( - (context, _) => FieldEditorPannelBloc(context), - ); - - getIt.registerFactoryParam( - (typeOption, _) => DateTypeOptionBloc(typeOption: typeOption), - ); - - getIt.registerFactoryParam( - (typeOption, _) => NumberTypeOptionBloc(typeOption: typeOption), - ); - getIt.registerFactoryParam( (gridId, cache) => GridPropertyBloc(gridId: gridId, fieldCache: cache), ); 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/user/presentation/skip_log_in_screen.dart b/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart index bad8000643..4a78815beb 100644 --- a/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart +++ b/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart @@ -88,8 +88,9 @@ class _SkipLogInScreenState extends State { } _launchURL(String url) async { - if (await canLaunch(url)) { - await launch(url); + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); } else { throw 'Could not launch $url'; } diff --git a/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart b/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart index 663e630a6a..e5eabe98e4 100644 --- a/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + import 'package:app_flowy/plugin/plugin.dart'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/app/app_listener.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/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_bloc.dart b/frontend/app_flowy/lib/workspace/application/doc/share_bloc.dart index c8fd8a2b96..e24411fdc0 100644 --- a/frontend/app_flowy/lib/workspace/application/doc/share_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/doc/share_bloc.dart @@ -1,3 +1,6 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:app_flowy/startup/tasks/rust_sdk.dart'; import 'package:app_flowy/workspace/application/doc/share_service.dart'; import 'package:app_flowy/workspace/application/markdown/delta_markdown.dart'; import 'package:flowy_sdk/protobuf/flowy-text-block/entities.pb.dart'; @@ -33,8 +36,30 @@ class DocShareBloc extends Bloc { ExportData _convertDeltaToMarkdown(ExportData value) { final result = deltaToMarkdown(value.data); value.data = result; + writeFile(result); return value; } + + Future get _exportDir async { + Directory documentsDir = await appFlowyDocumentDirectory(); + + return documentsDir; + } + + Future get _localPath async { + final dir = await _exportDir; + return dir.path; + } + + Future get _localFile async { + final path = await _localPath; + return File('$path/${view.name}.md'); + } + + Future writeFile(String md) async { + final file = await _localFile; + return file.writeAsString(md); + } } @freezed 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/cell_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart index 07be14efa2..68f8eada78 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart @@ -10,13 +10,13 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; - import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart'; import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; - +import 'dart:convert' show utf8; part 'cell_service.freezed.dart'; part 'data_loader.dart'; part 'context_builder.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart index c7faff5311..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 @@ -1,8 +1,9 @@ part of 'cell_service.dart'; -typedef GridCellContext = _GridCellContext; +typedef GridCellContext = _GridCellContext; typedef GridSelectOptionCellContext = _GridCellContext; -typedef GridDateCellContext = _GridCellContext; +typedef GridDateCellContext = _GridCellContext; +typedef GridURLCellContext = _GridCellContext; class GridCellContextBuilder { final GridCellCache _cellCache; @@ -16,61 +17,100 @@ class GridCellContextBuilder { _GridCellContext build() { switch (_gridCell.field.fieldType) { case FieldType.Checkbox: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + parser: StringCellDataParser(), + ); return GridCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: CellDataLoader(gridCell: _gridCell), + cellDataLoader: cellDataLoader, cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); case FieldType.DateTime: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + parser: DateCellDataParser(), + config: const GridCellDataConfig(reloadOnFieldChanged: true), + ); + return GridDateCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: DateCellDataLoader(gridCell: _gridCell), + cellDataLoader: cellDataLoader, cellDataPersistence: DateCellDataPersistence(gridCell: _gridCell), ); case FieldType.Number: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + parser: StringCellDataParser(), + config: const GridCellDataConfig(reloadOnCellChanged: true, reloadOnFieldChanged: true), + ); return GridCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: CellDataLoader(gridCell: _gridCell, reloadOnCellChanged: true), + cellDataLoader: cellDataLoader, cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); case FieldType.RichText: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + parser: StringCellDataParser(), + ); return GridCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: CellDataLoader(gridCell: _gridCell), + cellDataLoader: cellDataLoader, cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); case FieldType.MultiSelect: case FieldType.SingleSelect: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + parser: SelectOptionCellDataParser(), + config: const GridCellDataConfig(reloadOnFieldChanged: true), + ); + return GridSelectOptionCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: SelectOptionCellDataLoader(gridCell: _gridCell), + cellDataLoader: cellDataLoader, + cellDataPersistence: CellDataPersistence(gridCell: _gridCell), + ); + + case FieldType.URL: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + parser: URLCellDataParser(), + ); + return GridURLCellContext( + gridCell: _gridCell, + cellCache: _cellCache, + cellDataLoader: cellDataLoader, cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); - default: - throw UnimplementedError; } + throw UnimplementedError; } } +// T: the type of the CellData +// D: the type of the data that will be save to disk // ignore: must_be_immutable class _GridCellContext extends Equatable { final GridCell gridCell; final GridCellCache cellCache; final GridCellCacheKey _cacheKey; - final _GridCellDataLoader cellDataLoader; + final IGridCellDataLoader cellDataLoader; final _GridCellDataPersistence cellDataPersistence; final FieldService _fieldService; late final CellListener _cellListener; - late final ValueNotifier _cellDataNotifier; + late final ValueNotifier? _cellDataNotifier; bool isListening = false; VoidCallback? _onFieldChangedFn; - Timer? _delayOperation; + Timer? _loadDataOperation; + Timer? _saveDataOperation; _GridCellContext({ required this.gridCell, @@ -100,7 +140,7 @@ class _GridCellContext extends Equatable { FieldType get fieldType => gridCell.field.fieldType; - VoidCallback? startListening({required void Function(T) onCellChanged}) { + VoidCallback? startListening({required void Function(T?) onCellChanged}) { if (isListening) { Log.error("Already started. It seems like you should call clone first"); return null; @@ -124,52 +164,64 @@ class _GridCellContext extends Equatable { } onCellChangedFn() { - final value = _cellDataNotifier.value; - if (value is T) { - onCellChanged(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() { + T? getCellData({bool loadIfNoCache = true}) { final data = cellCache.get(_cacheKey); - if (data == null) { + if (data == null && loadIfNoCache) { _loadData(); } return data; } - Future, FlowyError>> getTypeOptionData() { - return _fieldService.getTypeOptionData(fieldType: fieldType); + Future> getTypeOptionData() { + return _fieldService.getFieldTypeOptionData(fieldType: fieldType); } - Future> saveCellData(D data) { - return cellDataPersistence.save(data); + void saveCellData(D data, {bool deduplicate = false, void Function(Option)? resultCallback}) async { + if (deduplicate) { + _loadDataOperation?.cancel(); + _loadDataOperation = Timer(const Duration(milliseconds: 300), () async { + final result = await cellDataPersistence.save(data); + if (resultCallback != null) { + resultCallback(result); + } + }); + } else { + final result = await cellDataPersistence.save(data); + if (resultCallback != null) { + resultCallback(result); + } + } } void _loadData() { - _delayOperation?.cancel(); - _delayOperation = Timer(const Duration(milliseconds: 10), () { + _loadDataOperation?.cancel(); + _loadDataOperation = Timer(const Duration(milliseconds: 10), () { cellDataLoader.loadData().then((data) { - _cellDataNotifier.value = data; + _cellDataNotifier?.value = data; cellCache.insert(GridCellCacheData(key: _cacheKey, object: data)); }); }); } void dispose() { - _delayOperation?.cancel(); + _cellListener.stop(); + _loadDataOperation?.cancel(); + _saveDataOperation?.cancel(); if (_onFieldChangedFn != null) { cellCache.removeFieldListener(_cacheKey, _onFieldChangedFn!); diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_cache.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_cache.dart index 409c236881..5e09573506 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_cache.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_cache.dart @@ -104,6 +104,8 @@ class GridCellCache { } Future dispose() async { + _fieldListenerByFieldId.clear(); + _cellDataByFieldId.clear(); fieldDelegate.dispose(); } } 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 f625af9ad0..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 @@ -1,94 +1,78 @@ part of 'cell_service.dart'; -abstract class GridCellDataConfig { +abstract class IGridCellDataConfig { // The cell data will reload if it receives the field's change notification. bool get reloadOnFieldChanged; - // The cell data will reload if it receives the cell's change notification. - // For example, the number cell should be reloaded after user input the number. + // When the reloadOnCellChanged is true, it will load the cell data after user input. + // For example: The number cell reload the cell data that carries the format // user input: 12 // cell display: $12 bool get reloadOnCellChanged; } -class DefaultCellDataConfig implements GridCellDataConfig { +class GridCellDataConfig implements IGridCellDataConfig { @override final bool reloadOnCellChanged; @override final bool reloadOnFieldChanged; - DefaultCellDataConfig({ + const GridCellDataConfig({ this.reloadOnCellChanged = false, this.reloadOnFieldChanged = false, }); } -abstract class _GridCellDataLoader { +abstract class IGridCellDataLoader { Future loadData(); - GridCellDataConfig get config; + IGridCellDataConfig get config; } -class CellDataLoader extends _GridCellDataLoader { +abstract class ICellDataParser { + T? parserData(List data); +} + +class GridCellDataLoader extends IGridCellDataLoader { final CellService service = CellService(); final GridCell gridCell; - final GridCellDataConfig _config; - - CellDataLoader({ - required this.gridCell, - bool reloadOnCellChanged = false, - }) : _config = DefaultCellDataConfig(reloadOnCellChanged: reloadOnCellChanged); + final ICellDataParser parser; @override - Future loadData() { + final IGridCellDataConfig config; + + GridCellDataLoader({ + required this.gridCell, + required this.parser, + this.config = const GridCellDataConfig(), + }); + + @override + Future loadData() { final fut = service.getCell( gridId: gridCell.gridId, fieldId: gridCell.field.id, rowId: gridCell.rowId, ); - return fut.then((result) { - return result.fold((data) => data, (err) { + return fut.then( + (result) => result.fold((Cell cell) { + try { + return parser.parserData(cell.data); + } catch (e, s) { + Log.error('$parser parser cellData failed, $e'); + Log.error('Stack trace \n $s'); + return null; + } + }, (err) { Log.error(err); return null; - }); - }); - } - - @override - GridCellDataConfig get config => _config; -} - -class DateCellDataLoader extends _GridCellDataLoader { - final GridCell gridCell; - final GridCellDataConfig _config; - DateCellDataLoader({ - required this.gridCell, - }) : _config = DefaultCellDataConfig(reloadOnFieldChanged: true); - - @override - GridCellDataConfig get config => _config; - - @override - Future loadData() { - final payload = CellIdentifierPayload.create() - ..gridId = gridCell.gridId - ..fieldId = gridCell.field.id - ..rowId = gridCell.rowId; - - return GridEventGetDateCellData(payload).send().then((result) { - return result.fold( - (data) => data, - (err) { - Log.error(err); - return null; - }, - ); - }); + }), + ); } } -class SelectOptionCellDataLoader extends _GridCellDataLoader { +class SelectOptionCellDataLoader extends IGridCellDataLoader { final SelectOptionService service; final GridCell gridCell; SelectOptionCellDataLoader({ @@ -108,5 +92,43 @@ class SelectOptionCellDataLoader extends _GridCellDataLoader DefaultCellDataConfig(); + IGridCellDataConfig get config => const GridCellDataConfig(reloadOnFieldChanged: true); +} + +class StringCellDataParser implements ICellDataParser { + @override + String? parserData(List 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); + } +} + +class SelectOptionCellDataParser implements ICellDataParser { + @override + SelectOptionCellData? parserData(List data) { + if (data.isEmpty) { + return null; + } + return SelectOptionCellData.fromBuffer(data); + } +} + +class URLCellDataParser 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/checkbox_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart index cecfaa2f04..b8e2b13bbc 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart @@ -1,4 +1,3 @@ -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -16,15 +15,15 @@ class CheckboxCellBloc extends Bloc { }) : super(CheckboxCellState.initial(cellContext)) { on( (event, emit) async { - await event.map( - initial: (_Initial value) { + await event.when( + initial: () { _startListening(); }, - select: (_Selected value) async { + select: () async { _updateCellData(); }, - didReceiveCellUpdate: (_DidReceiveCellUpdate value) { - emit(state.copyWith(isSelected: _isSelected(value.cell))); + didReceiveCellUpdate: (cellData) { + emit(state.copyWith(isSelected: _isSelected(cellData))); }, ); }, @@ -43,9 +42,9 @@ class CheckboxCellBloc extends Bloc { } void _startListening() { - _onCellChangedFn = cellContext.startListening(onCellChanged: ((cell) { + _onCellChangedFn = cellContext.startListening(onCellChanged: ((cellData) { if (!isClosed) { - add(CheckboxCellEvent.didReceiveCellUpdate(cell)); + add(CheckboxCellEvent.didReceiveCellUpdate(cellData)); } })); } @@ -59,7 +58,7 @@ class CheckboxCellBloc extends Bloc { class CheckboxCellEvent with _$CheckboxCellEvent { const factory CheckboxCellEvent.initial() = _Initial; const factory CheckboxCellEvent.select() = _Selected; - const factory CheckboxCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate; + const factory CheckboxCellEvent.didReceiveCellUpdate(String? cellData) = _DidReceiveCellUpdate; } @freezed @@ -73,7 +72,6 @@ class CheckboxCellState with _$CheckboxCellState { } } -bool _isSelected(Cell? cell) { - final content = cell?.content ?? ""; - return content == "Yes"; +bool _isSelected(String? cellData) { + return cellData == "Yes"; } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart index a0ba35f9d6..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 @@ -1,6 +1,9 @@ +import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; +import 'package:easy_localization/easy_localization.dart' show StringTranslateExtension; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -34,10 +37,10 @@ class DateCalBloc extends Bloc { setFocusedDay: (focusedDay) { 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)); + didReceiveCellUpdate: (DateCellData? cellData) { + 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); @@ -49,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)); }, ); }, @@ -57,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)) { @@ -75,30 +83,44 @@ 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; } - final result = await cellContext.saveCellData(newDateData); - result.fold( - () => emit(state.copyWith( - dateData: Some(newDateData), - timeFormatError: none(), - )), - (err) { - switch (ErrorCode.valueOf(err.code)!) { - case ErrorCode.InvalidDateTimeFormat: - emit(state.copyWith( - dateData: Some(newDateData), - timeFormatError: Some(err.toString()), - )); - break; - default: - Log.error(err); - } - }, - ); + updateCalData(Option calData, Option timeFormatError) { + if (!isClosed) add(DateCalEvent.didUpdateCalData(calData, timeFormatError)); + } + + cellContext.saveCellData(newCalData, resultCallback: (result) { + result.fold( + () => updateCalData(Some(newCalData), none()), + (err) { + switch (ErrorCode.valueOf(err.code)!) { + case ErrorCode.InvalidDateTimeFormat: + updateCalData(none(), Some(timeFormatPrompt(err))); + break; + default: + Log.error(err); + } + }, + ); + }); + } + + String timeFormatPrompt(FlowyError error) { + String msg = LocaleKeys.grid_field_invalidTimeFormat.tr() + ". "; + switch (state.dateTypeOption.timeFormat) { + case TimeFormat.TwelveHour: + msg = msg + "e.g. 01: 00 AM"; + break; + case TimeFormat.TwentyFourHour: + msg = msg + "e.g. 13: 00"; + break; + default: + break; + } + return msg; } @override @@ -149,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), ); } @@ -165,7 +187,9 @@ class DateCalEvent with _$DateCalEvent { const factory DateCalEvent.setDateFormat(DateFormat dateFormat) = _DateFormat; const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime; const factory DateCalEvent.setTime(String time) = _Time; - const factory DateCalEvent.didReceiveCellUpdate(DateCellData data) = _DidReceiveCellUpdate; + const factory DateCalEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate; + const factory DateCalEvent.didUpdateCalData(Option data, Option timeFormatError) = + _DidUpdateCalData; } @freezed @@ -175,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 4b068dd289..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 { @@ -16,7 +15,9 @@ class DateCellBloc extends Bloc { (event, emit) async { event.when( initial: () => _startListening(), - didReceiveCellUpdate: (DateCellData value) => emit(state.copyWith(data: Some(value))), + didReceiveCellUpdate: (DateCellData? cellData) { + emit(state.copyWith(data: cellData, dateStr: _dateStrFromCellData(cellData))); + }, didReceiveFieldUpdate: (Field value) => emit(state.copyWith(field: value)), ); }, @@ -47,28 +48,33 @@ class DateCellBloc extends Bloc { @freezed class DateCellEvent with _$DateCellEvent { const factory DateCellEvent.initial() = _InitialCell; - const factory DateCellEvent.didReceiveCellUpdate(DateCellData data) = _DidReceiveCellUpdate; + const factory DateCellEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate; const factory DateCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate; } @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 217ae4d384..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,7 +1,8 @@ -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; +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'; @@ -15,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.cell.content)); + 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) { @@ -46,9 +50,9 @@ class NumberCellBloc extends Bloc { void _startListening() { _onCellChangedFn = cellContext.startListening( - onCellChanged: ((cell) { + onCellChanged: ((cellContent) { if (!isClosed) { - add(NumberCellEvent.didReceiveCellUpdate(cell)); + add(NumberCellEvent.didReceiveCellUpdate(left(cellContent ?? ""))); } }), ); @@ -59,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(Cell cell) = _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 cell = context.getCellData(); - return NumberCellState(content: cell?.content ?? ""); + final cellContent = context.getCellData() ?? ""; + return NumberCellState( + content: left(cellContent), + ); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/selection_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart similarity index 61% rename from frontend/app_flowy/lib/workspace/application/grid/cell/selection_cell_bloc.dart rename to frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart index 794ccd3002..c6393e4831 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/selection_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart @@ -4,16 +4,16 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; -part 'selection_cell_bloc.freezed.dart'; +part 'select_option_cell_bloc.freezed.dart'; -class SelectionCellBloc extends Bloc { +class SelectOptionCellBloc extends Bloc { final GridSelectOptionCellContext cellContext; void Function()? _onCellChangedFn; - SelectionCellBloc({ + SelectOptionCellBloc({ required this.cellContext, - }) : super(SelectionCellState.initial(cellContext)) { - on( + }) : super(SelectOptionCellState.initial(cellContext)) { + on( (event, emit) async { await event.map( initial: (_InitialCell value) async { @@ -21,7 +21,6 @@ class SelectionCellBloc extends Bloc { }, didReceiveOptions: (_DidReceiveOptions value) { emit(state.copyWith( - options: value.options, selectedOptions: value.selectedOptions, )); }, @@ -44,9 +43,8 @@ class SelectionCellBloc extends Bloc { _onCellChangedFn = cellContext.startListening( onCellChanged: ((selectOptionContext) { if (!isClosed) { - add(SelectionCellEvent.didReceiveOptions( - selectOptionContext.options, - selectOptionContext.selectOptions, + add(SelectOptionCellEvent.didReceiveOptions( + selectOptionContext?.selectOptions ?? [], )); } }), @@ -55,26 +53,23 @@ class SelectionCellBloc extends Bloc { } @freezed -class SelectionCellEvent with _$SelectionCellEvent { - const factory SelectionCellEvent.initial() = _InitialCell; - const factory SelectionCellEvent.didReceiveOptions( - List options, +class SelectOptionCellEvent with _$SelectOptionCellEvent { + const factory SelectOptionCellEvent.initial() = _InitialCell; + const factory SelectOptionCellEvent.didReceiveOptions( List selectedOptions, ) = _DidReceiveOptions; } @freezed -class SelectionCellState with _$SelectionCellState { - const factory SelectionCellState({ - required List options, +class SelectOptionCellState with _$SelectOptionCellState { + const factory SelectOptionCellState({ required List selectedOptions, - }) = _SelectionCellState; + }) = _SelectOptionCellState; - factory SelectionCellState.initial(GridSelectOptionCellContext context) { + factory SelectOptionCellState.initial(GridSelectOptionCellContext context) { final data = context.getCellData(); - return SelectionCellState( - options: data?.options ?? [], + return SelectOptionCellState( selectedOptions: data?.selectOptions ?? [], ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart similarity index 80% rename from frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart rename to frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart index cc7af58531..87eabdf759 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; @@ -6,23 +7,28 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; import 'select_option_service.dart'; +import 'package:collection/collection.dart'; -part 'selection_editor_bloc.freezed.dart'; +part 'select_option_editor_bloc.freezed.dart'; -class SelectOptionEditorBloc extends Bloc { +class SelectOptionCellEditorBloc extends Bloc { final SelectOptionService _selectOptionService; final GridSelectOptionCellContext cellContext; + late final GridFieldsListener _fieldListener; void Function()? _onCellChangedFn; + Timer? _delayOperation; - SelectOptionEditorBloc({ + SelectOptionCellEditorBloc({ required this.cellContext, }) : _selectOptionService = SelectOptionService(gridCell: cellContext.gridCell), + _fieldListener = GridFieldsListener(gridId: cellContext.gridId), super(SelectOptionEditorState.initial(cellContext)) { on( (event, emit) async { await event.map( initial: (_Initial value) async { _startListening(); + _loadOptions(); }, didReceiveOptions: (_DidReceiveOptions value) { final result = _makeOptions(state.filter, value.options); @@ -62,6 +68,8 @@ class SelectOptionEditorBloc extends Bloc add(SelectOptionEditorEvent.didReceiveOptions(data.options, data.selectOptions)), + (err) { + Log.error(err); + return null; + }, + ); + }); + }); + } + _MakeOptionResult _makeOptions(Option filter, List allOptions) { final List options = List.from(allOptions); Option createOption = filter; @@ -134,13 +160,21 @@ class SelectOptionEditorBloc extends Bloc Log.error(err), + ); + }); } } @@ -167,7 +201,7 @@ class SelectOptionEditorState with _$SelectOptionEditorState { }) = _SelectOptionEditorState; factory SelectOptionEditorState.initial(GridSelectOptionCellContext context) { - final data = context.getCellData(); + final data = context.getCellData(loadIfNoCache: false); return SelectOptionEditorState( options: data?.options ?? [], allOptions: data?.options ?? [], diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart index 0b8b8be5e0..e3b7fd2dca 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart @@ -1,4 +1,3 @@ -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -14,21 +13,16 @@ class TextCellBloc extends Bloc { }) : super(TextCellState.initial(cellContext)) { on( (event, emit) async { - await event.map( - initial: (_InitialCell value) async { + await event.when( + initial: () async { _startListening(); }, - updateText: (_UpdateText value) { - cellContext.saveCellData(value.text); - emit(state.copyWith(content: value.text)); + updateText: (text) { + cellContext.saveCellData(text); + emit(state.copyWith(content: text)); }, - didReceiveCellData: (_DidReceiveCellData value) { - emit(state.copyWith(content: value.cellData.cell?.content ?? "")); - }, - didReceiveCellUpdate: (_DidReceiveCellUpdate value) { - emit(state.copyWith( - content: value.cell.content, - )); + didReceiveCellUpdate: (content) { + emit(state.copyWith(content: content)); }, ); }, @@ -47,9 +41,9 @@ class TextCellBloc extends Bloc { void _startListening() { _onCellChangedFn = cellContext.startListening( - onCellChanged: ((cell) { + onCellChanged: ((cellContent) { if (!isClosed) { - add(TextCellEvent.didReceiveCellUpdate(cell)); + add(TextCellEvent.didReceiveCellUpdate(cellContent ?? "")); } }), ); @@ -59,8 +53,7 @@ class TextCellBloc extends Bloc { @freezed class TextCellEvent with _$TextCellEvent { const factory TextCellEvent.initial() = _InitialCell; - const factory TextCellEvent.didReceiveCellData(GridCell cellData) = _DidReceiveCellData; - const factory TextCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate; + const factory TextCellEvent.didReceiveCellUpdate(String cellContent) = _DidReceiveCellUpdate; const factory TextCellEvent.updateText(String text) = _UpdateText; } @@ -71,6 +64,6 @@ class TextCellState with _$TextCellState { }) = _TextCellState; factory TextCellState.initial(GridCellContext context) => TextCellState( - content: context.getCellData()?.content ?? "", + content: context.getCellData() ?? "", ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart new file mode 100644 index 0000000000..e1fe39c3bf --- /dev/null +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart @@ -0,0 +1,77 @@ +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'dart:async'; +import 'cell_service/cell_service.dart'; + +part 'url_cell_bloc.freezed.dart'; + +class URLCellBloc extends Bloc { + final GridURLCellContext cellContext; + void Function()? _onCellChangedFn; + URLCellBloc({ + required this.cellContext, + }) : super(URLCellState.initial(cellContext)) { + on( + (event, emit) async { + event.when( + initial: () { + _startListening(); + }, + didReceiveCellUpdate: (cellData) { + emit(state.copyWith( + content: cellData?.content ?? "", + url: cellData?.url ?? "", + )); + }, + updateURL: (String url) { + cellContext.saveCellData(url, deduplicate: true); + }, + ); + }, + ); + } + + @override + Future close() async { + if (_onCellChangedFn != null) { + cellContext.removeListener(_onCellChangedFn!); + _onCellChangedFn = null; + } + cellContext.dispose(); + return super.close(); + } + + void _startListening() { + _onCellChangedFn = cellContext.startListening( + onCellChanged: ((cellData) { + if (!isClosed) { + add(URLCellEvent.didReceiveCellUpdate(cellData)); + } + }), + ); + } +} + +@freezed +class URLCellEvent with _$URLCellEvent { + const factory URLCellEvent.initial() = _InitialCell; + const factory URLCellEvent.updateURL(String url) = _UpdateURL; + const factory URLCellEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate; +} + +@freezed +class URLCellState with _$URLCellState { + const factory URLCellState({ + required String content, + required String url, + }) = _URLCellState; + + factory URLCellState.initial(GridURLCellContext context) { + final cellData = context.getCellData(); + return URLCellState( + content: cellData?.content ?? "", + url: cellData?.url ?? "", + ); + } +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_editor_bloc.dart new file mode 100644 index 0000000000..6e4990943f --- /dev/null +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_editor_bloc.dart @@ -0,0 +1,73 @@ +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'dart:async'; +import 'cell_service/cell_service.dart'; + +part 'url_cell_editor_bloc.freezed.dart'; + +class URLCellEditorBloc extends Bloc { + final GridURLCellContext cellContext; + void Function()? _onCellChangedFn; + URLCellEditorBloc({ + required this.cellContext, + }) : super(URLCellEditorState.initial(cellContext)) { + on( + (event, emit) async { + event.when( + initial: () { + _startListening(); + }, + updateText: (text) { + cellContext.saveCellData(text, deduplicate: true); + emit(state.copyWith(content: text)); + }, + didReceiveCellUpdate: (cellData) { + emit(state.copyWith(content: cellData?.content ?? "")); + }, + ); + }, + ); + } + + @override + Future close() async { + if (_onCellChangedFn != null) { + cellContext.removeListener(_onCellChangedFn!); + _onCellChangedFn = null; + } + cellContext.dispose(); + return super.close(); + } + + void _startListening() { + _onCellChangedFn = cellContext.startListening( + onCellChanged: ((cellData) { + if (!isClosed) { + add(URLCellEditorEvent.didReceiveCellUpdate(cellData)); + } + }), + ); + } +} + +@freezed +class URLCellEditorEvent with _$URLCellEditorEvent { + const factory URLCellEditorEvent.initial() = _InitialCell; + const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate; + const factory URLCellEditorEvent.updateText(String text) = _UpdateText; +} + +@freezed +class URLCellEditorState with _$URLCellEditorState { + const factory URLCellEditorState({ + required String content, + }) = _URLCellEditorState; + + factory URLCellEditorState.initial(GridURLCellContext context) { + final cellData = context.getCellData(); + return URLCellEditorState( + content: cellData?.content ?? "", + ); + } +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_action_sheet_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_action_sheet_bloc.dart index 6513fe85c3..25c9cbfc0d 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_action_sheet_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_action_sheet_bloc.dart @@ -11,7 +11,7 @@ class FieldActionSheetBloc extends Bloc( (event, emit) async { await event.map( @@ -67,14 +67,14 @@ class FieldActionSheetEvent with _$FieldActionSheetEvent { @freezed class FieldActionSheetState with _$FieldActionSheetState { const factory FieldActionSheetState({ - required EditFieldContext editContext, + required FieldTypeOptionData fieldTypeOptionData, required String errorText, required String fieldName, }) = _FieldActionSheetState; - factory FieldActionSheetState.initial(EditFieldContext editContext) => FieldActionSheetState( - editContext: editContext, + factory FieldActionSheetState.initial(FieldTypeOptionData data) => FieldActionSheetState( + fieldTypeOptionData: data, errorText: '', - fieldName: editContext.gridField.name, + fieldName: data.field_2.name, ); } 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 f5fc41e57f..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 @@ -19,18 +19,20 @@ class FieldCellBloc extends Bloc { super(FieldCellState.initial(cellContext)) { on( (event, emit) async { - await event.map( - initial: (_InitialCell value) async { + event.when( + initial: () { _startListening(); }, - didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) { - emit(state.copyWith(field: value.field)); + didReceiveFieldUpdate: (field) { + emit(state.copyWith(field: cellContext.field)); }, - updateWidth: (_UpdateWidth value) { - final defaultWidth = state.field.width.toDouble(); - final width = defaultWidth + value.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/field_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart index d52fb8c312..2815d5519d 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart @@ -1,41 +1,31 @@ -import 'dart:typed_data'; -import 'package:flowy_sdk/log.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; import 'field_service.dart'; import 'package:dartz/dartz.dart'; -import 'package:protobuf/protobuf.dart'; - part 'field_editor_bloc.freezed.dart'; class FieldEditorBloc extends Bloc { - final String gridId; - final EditFieldContextLoader _loader; - FieldEditorBloc({ - required this.gridId, - required EditFieldContextLoader fieldLoader, - }) : _loader = fieldLoader, - super(FieldEditorState.initial(gridId)) { + required String gridId, + required String fieldName, + required IFieldContextLoader fieldContextLoader, + }) : super(FieldEditorState.initial(gridId, fieldName, fieldContextLoader)) { on( (event, emit) async { - await event.map( - initial: (_InitialField value) async { - await _getEditFieldContext(emit); + await event.when( + initial: () async { + final fieldContext = GridFieldContext(gridId: gridId, loader: fieldContextLoader); + await fieldContext.loadData().then((result) { + result.fold( + (l) => emit(state.copyWith(fieldContext: Some(fieldContext), name: fieldContext.field.name)), + (r) => null, + ); + }); }, - updateName: (_UpdateName value) { - final newContext = _updateEditContext(name: value.name); - emit(state.copyWith(editFieldContext: newContext)); - }, - updateField: (_UpdateField value) { - final newContext = _updateEditContext(field: value.field, typeOptionData: value.typeOptionData); - - emit(state.copyWith(editFieldContext: newContext)); - }, - done: (_Done value) async { - await _saveField(emit); + updateName: (name) { + state.fieldContext.fold(() => null, (fieldContext) => fieldContext.fieldName = name); + emit(state.copyWith(name: name)); }, ); }, @@ -46,78 +36,12 @@ class FieldEditorBloc extends Bloc { Future close() async { return super.close(); } - - Option _updateEditContext({ - String? name, - Field? field, - List? typeOptionData, - }) { - return state.editFieldContext.fold( - () => none(), - (context) { - context.freeze(); - final newContext = context.rebuild((newContext) { - newContext.gridField.rebuild((newField) { - if (name != null) { - newField.name = name; - } - - newContext.gridField = newField; - }); - - if (field != null) { - newContext.gridField = field; - } - - if (typeOptionData != null) { - newContext.typeOptionData = typeOptionData; - } - }); - - FieldService.insertField( - gridId: gridId, - field: newContext.gridField, - typeOptionData: newContext.typeOptionData, - ); - - return Some(newContext); - }, - ); - } - - Future _saveField(Emitter emit) async { - await state.editFieldContext.fold( - () async => null, - (context) async { - final result = await FieldService.insertField( - gridId: gridId, - field: context.gridField, - typeOptionData: context.typeOptionData, - ); - result.fold((l) => null, (r) => null); - }, - ); - } - - Future _getEditFieldContext(Emitter emit) async { - final result = await _loader.load(); - result.fold( - (context) { - emit(state.copyWith( - editFieldContext: Some(context), - )); - }, - (err) => Log.error(err), - ); - } } @freezed class FieldEditorEvent with _$FieldEditorEvent { const factory FieldEditorEvent.initial() = _InitialField; const factory FieldEditorEvent.updateName(String name) = _UpdateName; - const factory FieldEditorEvent.updateField(Field field, Uint8List typeOptionData) = _UpdateField; - const factory FieldEditorEvent.done() = _Done; } @freezed @@ -125,12 +49,14 @@ class FieldEditorState with _$FieldEditorState { const factory FieldEditorState({ required String gridId, required String errorText, - required Option editFieldContext, + required String name, + required Option fieldContext, }) = _FieldEditorState; - factory FieldEditorState.initial(String gridId) => FieldEditorState( + factory FieldEditorState.initial(String gridId, String fieldName, IFieldContextLoader loader) => FieldEditorState( gridId: gridId, - editFieldContext: none(), + fieldContext: none(), errorText: '', + name: fieldName, ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_pannel_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_pannel_bloc.dart index 09d0268d63..c6d48c9d18 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_pannel_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_pannel_bloc.dart @@ -1,24 +1,29 @@ -import 'dart:typed_data'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; +import 'field_service.dart'; + part 'field_editor_pannel_bloc.freezed.dart'; class FieldEditorPannelBloc extends Bloc { - FieldEditorPannelBloc(EditFieldContext editContext) : super(FieldEditorPannelState.initial(editContext)) { + final GridFieldContext _fieldContext; + void Function()? _fieldListenFn; + + FieldEditorPannelBloc(GridFieldContext fieldContext) + : _fieldContext = fieldContext, + super(FieldEditorPannelState.initial(fieldContext)) { on( (event, emit) async { - await event.map( - toFieldType: (_ToFieldType value) async { - emit(state.copyWith( - field: value.field, - typeOptionData: Uint8List.fromList(value.typeOptionData), - )); + event.when( + initial: () { + _fieldListenFn = fieldContext.addFieldListener((field) { + add(FieldEditorPannelEvent.didReceiveFieldUpdated(field)); + }); }, - didUpdateTypeOptionData: (_DidUpdateTypeOptionData value) { - emit(state.copyWith(typeOptionData: value.typeOptionData)); + didReceiveFieldUpdated: (field) { + emit(state.copyWith(field: field)); }, ); }, @@ -27,27 +32,26 @@ class FieldEditorPannelBloc extends Bloc close() async { + if (_fieldListenFn != null) { + _fieldContext.removeFieldListener(_fieldListenFn!); + } return super.close(); } } @freezed class FieldEditorPannelEvent with _$FieldEditorPannelEvent { - const factory FieldEditorPannelEvent.toFieldType(Field field, List typeOptionData) = _ToFieldType; - const factory FieldEditorPannelEvent.didUpdateTypeOptionData(Uint8List typeOptionData) = _DidUpdateTypeOptionData; + const factory FieldEditorPannelEvent.initial() = _Initial; + const factory FieldEditorPannelEvent.didReceiveFieldUpdated(Field field) = _DidReceiveFieldUpdated; } @freezed class FieldEditorPannelState with _$FieldEditorPannelState { const factory FieldEditorPannelState({ - required String gridId, required Field field, - required Uint8List typeOptionData, }) = _FieldEditorPannelState; - factory FieldEditorPannelState.initial(EditFieldContext context) => FieldEditorPannelState( - gridId: context.gridId, - field: context.gridField, - typeOptionData: Uint8List.fromList(context.typeOptionData), + factory FieldEditorPannelState.initial(GridFieldContext fieldContext) => FieldEditorPannelState( + field: fieldContext.field, ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart index 70c6caf9f8..36547071b9 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart @@ -1,9 +1,12 @@ import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; +import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:protobuf/protobuf.dart'; part 'field_service.freezed.dart'; class FieldService { @@ -12,24 +15,6 @@ class FieldService { FieldService({required this.gridId, required this.fieldId}); - Future> switchToField(FieldType fieldType) { - final payload = EditFieldPayload.create() - ..gridId = gridId - ..fieldId = fieldId - ..fieldType = fieldType; - - return GridEventSwitchToField(payload).send(); - } - - Future> getEditFieldContext(FieldType fieldType) { - final payload = EditFieldPayload.create() - ..gridId = gridId - ..fieldId = fieldId - ..fieldType = fieldType; - - return GridEventGetEditFieldContext(payload).send(); - } - Future> moveField(int fromIndex, int toIndex) { final payload = MoveItemPayload.create() ..gridId = gridId @@ -128,7 +113,7 @@ class FieldService { return GridEventDuplicateField(payload).send(); } - Future, FlowyError>> getTypeOptionData({ + Future> getFieldTypeOptionData({ required FieldType fieldType, }) { final payload = EditFieldPayload.create() @@ -137,7 +122,7 @@ class FieldService { ..fieldType = fieldType; return GridEventGetFieldTypeOption(payload).send().then((result) { return result.fold( - (data) => left(data.typeOptionData), + (data) => left(data), (err) => right(err), ); }); @@ -152,59 +137,162 @@ class GridFieldCellContext with _$GridFieldCellContext { }) = _GridFieldCellContext; } -abstract class EditFieldContextLoader { - Future> load(); +abstract class IFieldContextLoader { + String get gridId; + Future> load(); - Future> switchToField(String fieldId, FieldType fieldType); + Future> switchToField(String fieldId, FieldType fieldType) { + final payload = EditFieldPayload.create() + ..gridId = gridId + ..fieldId = fieldId + ..fieldType = fieldType; + + return GridEventSwitchToField(payload).send(); + } } -class NewFieldContextLoader extends EditFieldContextLoader { +class NewFieldContextLoader extends IFieldContextLoader { + @override final String gridId; NewFieldContextLoader({ required this.gridId, }); @override - Future> load() { + Future> load() { final payload = EditFieldPayload.create() ..gridId = gridId ..fieldType = FieldType.RichText; - return GridEventGetEditFieldContext(payload).send(); - } - - @override - Future> switchToField(String fieldId, FieldType fieldType) { - final payload = EditFieldPayload.create() - ..gridId = gridId - ..fieldType = fieldType; - - return GridEventGetEditFieldContext(payload).send(); + return GridEventCreateFieldTypeOption(payload).send(); } } -class FieldContextLoaderAdaptor extends EditFieldContextLoader { +class FieldContextLoader extends IFieldContextLoader { + @override final String gridId; final Field field; - FieldContextLoaderAdaptor({ + FieldContextLoader({ required this.gridId, required this.field, }); @override - Future> load() { + Future> load() { final payload = EditFieldPayload.create() ..gridId = gridId ..fieldId = field.id ..fieldType = field.fieldType; - return GridEventGetEditFieldContext(payload).send(); - } - - @override - Future> switchToField(String fieldId, FieldType fieldType) async { - final fieldService = FieldService(gridId: gridId, fieldId: fieldId); - return fieldService.switchToField(fieldType); + return GridEventGetFieldTypeOption(payload).send(); + } +} + +class GridFieldContext { + final String gridId; + final IFieldContextLoader _loader; + + late FieldTypeOptionData _data; + ValueNotifier? _fieldNotifier; + + GridFieldContext({ + required this.gridId, + required IFieldContextLoader loader, + }) : _loader = loader; + + Future> loadData() async { + final result = await _loader.load(); + return result.fold( + (data) { + data.freeze(); + _data = data; + + if (_fieldNotifier == null) { + _fieldNotifier = ValueNotifier(data.field_2); + } else { + _fieldNotifier?.value = data.field_2; + } + + return left(unit); + }, + (err) { + Log.error(err); + return right(err); + }, + ); + } + + Field get field => _data.field_2; + + set field(Field field) { + _updateData(newField: field); + } + + List get typeOptionData => _data.typeOptionData; + + set fieldName(String name) { + _updateData(newName: name); + } + + set typeOptionData(List typeOptionData) { + _updateData(newTypeOptionData: typeOptionData); + } + + void _updateData({String? newName, Field? newField, List? newTypeOptionData}) { + _data = _data.rebuild((rebuildData) { + if (newName != null) { + rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) { + rebuildField.name = newName; + }); + } + + if (newField != null) { + rebuildData.field_2 = newField; + } + + if (newTypeOptionData != null) { + rebuildData.typeOptionData = newTypeOptionData; + } + }); + + if (_data.field_2 != _fieldNotifier?.value) { + _fieldNotifier?.value = _data.field_2; + } + + FieldService.insertField( + gridId: gridId, + field: field, + typeOptionData: typeOptionData, + ); + } + + Future switchToField(FieldType newFieldType) { + return _loader.switchToField(field.id, newFieldType).then((result) { + return result.fold( + (fieldTypeOptionData) { + _updateData( + newField: fieldTypeOptionData.field_2, + newTypeOptionData: fieldTypeOptionData.typeOptionData, + ); + }, + (err) { + Log.error(err); + }, + ); + }); + } + + void Function() addFieldListener(void Function(Field) callback) { + listener() { + callback(field); + } + + _fieldNotifier?.addListener(listener); + return listener; + } + + void removeFieldListener(void Function() listener) { + _fieldNotifier?.removeListener(listener); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart index c0a232a84f..8784422c54 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart @@ -1,3 +1,4 @@ +import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -5,8 +6,18 @@ import 'dart:async'; import 'package:protobuf/protobuf.dart'; part 'date_bloc.freezed.dart'; +typedef DateTypeOptionContext = TypeOptionContext; + +class DateTypeOptionDataBuilder extends TypeOptionDataBuilder { + @override + DateTypeOption fromBuffer(List buffer) { + return DateTypeOption.fromBuffer(buffer); + } +} + class DateTypeOptionBloc extends Bloc { - DateTypeOptionBloc({required DateTypeOption typeOption}) : super(DateTypeOptionState.initial(typeOption)) { + DateTypeOptionBloc({required DateTypeOptionContext typeOptionContext}) + : super(DateTypeOptionState.initial(typeOptionContext.typeOption)) { on( (event, emit) async { event.map( diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/field_option_pannel_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/field_option_pannel_bloc.dart deleted file mode 100644 index 04ae02309d..0000000000 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/field_option_pannel_bloc.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'dart:async'; -import 'package:dartz/dartz.dart'; -part 'field_option_pannel_bloc.freezed.dart'; - -class FieldOptionPannelBloc extends Bloc { - FieldOptionPannelBloc({required List options}) : super(FieldOptionPannelState.initial(options)) { - on( - (event, emit) async { - await event.map( - createOption: (_CreateOption value) async { - emit(state.copyWith(isEditingOption: false, newOptionName: Some(value.optionName))); - }, - beginAddingOption: (_BeginAddingOption value) { - emit(state.copyWith(isEditingOption: true, newOptionName: none())); - }, - endAddingOption: (_EndAddingOption value) { - emit(state.copyWith(isEditingOption: false, newOptionName: none())); - }, - updateOption: (_UpdateOption value) { - emit(state.copyWith(updateOption: Some(value.option))); - }, - deleteOption: (_DeleteOption value) { - emit(state.copyWith(deleteOption: Some(value.option))); - }, - ); - }, - ); - } - - @override - Future close() async { - return super.close(); - } -} - -@freezed -class FieldOptionPannelEvent with _$FieldOptionPannelEvent { - const factory FieldOptionPannelEvent.createOption(String optionName) = _CreateOption; - const factory FieldOptionPannelEvent.beginAddingOption() = _BeginAddingOption; - const factory FieldOptionPannelEvent.endAddingOption() = _EndAddingOption; - const factory FieldOptionPannelEvent.updateOption(SelectOption option) = _UpdateOption; - const factory FieldOptionPannelEvent.deleteOption(SelectOption option) = _DeleteOption; -} - -@freezed -class FieldOptionPannelState with _$FieldOptionPannelState { - const factory FieldOptionPannelState({ - required List options, - required bool isEditingOption, - required Option newOptionName, - required Option updateOption, - required Option deleteOption, - }) = _FieldOptionPannelState; - - factory FieldOptionPannelState.initial(List options) => FieldOptionPannelState( - options: options, - isEditingOption: false, - newOptionName: none(), - updateOption: none(), - deleteOption: none(), - ); -} diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_bloc.dart deleted file mode 100644 index 0d42612595..0000000000 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_bloc.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:flowy_sdk/log.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'dart:async'; -import 'package:protobuf/protobuf.dart'; -import 'type_option_service.dart'; - -part 'multi_select_bloc.freezed.dart'; - -class MultiSelectTypeOptionBloc extends Bloc { - final TypeOptionService service; - - MultiSelectTypeOptionBloc(TypeOptionContext typeOptionContext) - : service = TypeOptionService(gridId: typeOptionContext.gridId, fieldId: typeOptionContext.field.id), - super(MultiSelectTypeOptionState.initial(MultiSelectTypeOption.fromBuffer(typeOptionContext.data))) { - on( - (event, emit) async { - await event.map( - createOption: (_CreateOption value) async { - final result = await service.newOption(name: value.optionName); - result.fold( - (option) { - emit(state.copyWith(typeOption: _insertOption(option))); - }, - (err) => Log.error(err), - ); - }, - updateOption: (_UpdateOption value) async { - emit(state.copyWith(typeOption: _updateOption(value.option))); - }, - deleteOption: (_DeleteOption value) { - emit(state.copyWith(typeOption: _deleteOption(value.option))); - }, - ); - }, - ); - } - - @override - Future close() async { - return super.close(); - } - - MultiSelectTypeOption _insertOption(SelectOption option) { - state.typeOption.freeze(); - return state.typeOption.rebuild((typeOption) { - typeOption.options.insert(0, option); - }); - } - - MultiSelectTypeOption _updateOption(SelectOption option) { - state.typeOption.freeze(); - return state.typeOption.rebuild((typeOption) { - final index = typeOption.options.indexWhere((element) => element.id == option.id); - if (index != -1) { - typeOption.options[index] = option; - } - }); - } - - MultiSelectTypeOption _deleteOption(SelectOption option) { - state.typeOption.freeze(); - return state.typeOption.rebuild((typeOption) { - final index = typeOption.options.indexWhere((element) => element.id == option.id); - if (index != -1) { - typeOption.options.removeAt(index); - } - }); - } -} - -@freezed -class MultiSelectTypeOptionEvent with _$MultiSelectTypeOptionEvent { - const factory MultiSelectTypeOptionEvent.createOption(String optionName) = _CreateOption; - const factory MultiSelectTypeOptionEvent.updateOption(SelectOption option) = _UpdateOption; - const factory MultiSelectTypeOptionEvent.deleteOption(SelectOption option) = _DeleteOption; -} - -@freezed -class MultiSelectTypeOptionState with _$MultiSelectTypeOptionState { - const factory MultiSelectTypeOptionState({ - required MultiSelectTypeOption typeOption, - }) = _MultiSelectTypeOptionState; - - factory MultiSelectTypeOptionState.initial(MultiSelectTypeOption typeOption) => MultiSelectTypeOptionState( - typeOption: typeOption, - ); -} diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart new file mode 100644 index 0000000000..bb72277f0b --- /dev/null +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart @@ -0,0 +1,77 @@ +import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; +import 'package:flowy_sdk/log.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'dart:async'; +import 'package:protobuf/protobuf.dart'; +import 'select_option_type_option_bloc.dart'; +import 'type_option_service.dart'; + +class MultiSelectTypeOptionContext extends TypeOptionContext with SelectOptionTypeOptionAction { + final TypeOptionService service; + + MultiSelectTypeOptionContext({ + required MultiSelectTypeOptionDataBuilder dataBuilder, + required GridFieldContext fieldContext, + }) : service = TypeOptionService( + gridId: fieldContext.gridId, + fieldId: fieldContext.field.id, + ), + super(dataBuilder: dataBuilder, fieldContext: fieldContext); + + @override + List Function(SelectOption) get deleteOption { + return (SelectOption option) { + typeOption.freeze(); + typeOption = typeOption.rebuild((typeOption) { + final index = typeOption.options.indexWhere((element) => element.id == option.id); + if (index != -1) { + typeOption.options.removeAt(index); + } + }); + return typeOption.options; + }; + } + + @override + Future> Function(String) get insertOption { + return (String optionName) { + return service.newOption(name: optionName).then((result) { + return result.fold( + (option) { + typeOption.freeze(); + typeOption = typeOption.rebuild((typeOption) { + typeOption.options.insert(0, option); + }); + + return typeOption.options; + }, + (err) { + Log.error(err); + return typeOption.options; + }, + ); + }); + }; + } + + @override + List Function(SelectOption) get udpateOption { + return (SelectOption option) { + typeOption.freeze(); + typeOption = typeOption.rebuild((typeOption) { + final index = typeOption.options.indexWhere((element) => element.id == option.id); + if (index != -1) { + typeOption.options[index] = option; + } + }); + return typeOption.options; + }; + } +} + +class MultiSelectTypeOptionDataBuilder extends TypeOptionDataBuilder { + @override + MultiSelectTypeOption fromBuffer(List buffer) { + return MultiSelectTypeOption.fromBuffer(buffer); + } +} 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 a655ebc1e8..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,3 +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'; @@ -6,8 +8,18 @@ import 'package:protobuf/protobuf.dart'; part 'number_bloc.freezed.dart'; +typedef NumberTypeOptionContext = TypeOptionContext; + +class NumberTypeOptionDataBuilder extends TypeOptionDataBuilder { + @override + NumberTypeOption fromBuffer(List buffer) { + return NumberTypeOption.fromBuffer(buffer); + } +} + class NumberTypeOptionBloc extends Bloc { - NumberTypeOptionBloc({required NumberTypeOption typeOption}) : super(NumberTypeOptionState.initial(typeOption)) { + NumberTypeOptionBloc({required NumberTypeOptionContext typeOptionContext}) + : super(NumberTypeOptionState.initial(typeOptionContext.typeOption)) { on( (event, emit) async { event.map( 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/field/type_option/select_option_type_option_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart new file mode 100644 index 0000000000..d4290cd8ff --- /dev/null +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart @@ -0,0 +1,77 @@ +import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'dart:async'; +import 'package:dartz/dartz.dart'; +part 'select_option_type_option_bloc.freezed.dart'; + +abstract class SelectOptionTypeOptionAction { + Future> Function(String) get insertOption; + + List Function(SelectOption) get deleteOption; + + List Function(SelectOption) get udpateOption; +} + +class SelectOptionTypeOptionBloc extends Bloc { + final SelectOptionTypeOptionAction typeOptionAction; + + SelectOptionTypeOptionBloc({ + required List options, + required this.typeOptionAction, + }) : super(SelectOptionTypeOptionState.initial(options)) { + on( + (event, emit) async { + await event.when( + createOption: (optionName) async { + final List options = await typeOptionAction.insertOption(optionName); + emit(state.copyWith(options: options)); + }, + addingOption: () { + emit(state.copyWith(isEditingOption: true, newOptionName: none())); + }, + endAddingOption: () { + emit(state.copyWith(isEditingOption: false, newOptionName: none())); + }, + updateOption: (option) { + final List options = typeOptionAction.udpateOption(option); + emit(state.copyWith(options: options)); + }, + deleteOption: (option) { + final List options = typeOptionAction.deleteOption(option); + emit(state.copyWith(options: options)); + }, + ); + }, + ); + } + + @override + Future close() async { + return super.close(); + } +} + +@freezed +class SelectOptionTypeOptionEvent with _$SelectOptionTypeOptionEvent { + const factory SelectOptionTypeOptionEvent.createOption(String optionName) = _CreateOption; + const factory SelectOptionTypeOptionEvent.addingOption() = _AddingOption; + const factory SelectOptionTypeOptionEvent.endAddingOption() = _EndAddingOption; + const factory SelectOptionTypeOptionEvent.updateOption(SelectOption option) = _UpdateOption; + const factory SelectOptionTypeOptionEvent.deleteOption(SelectOption option) = _DeleteOption; +} + +@freezed +class SelectOptionTypeOptionState with _$SelectOptionTypeOptionState { + const factory SelectOptionTypeOptionState({ + required List options, + required bool isEditingOption, + required Option newOptionName, + }) = _SelectOptionTyepOptionState; + + factory SelectOptionTypeOptionState.initial(List options) => SelectOptionTypeOptionState( + options: options, + isEditingOption: false, + newOptionName: none(), + ); +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_bloc.dart deleted file mode 100644 index 36a2f956b3..0000000000 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_bloc.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'package:flowy_sdk/log.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'dart:async'; -import 'package:protobuf/protobuf.dart'; -import 'type_option_service.dart'; - -part 'single_select_bloc.freezed.dart'; - -class SingleSelectTypeOptionBloc extends Bloc { - final TypeOptionService service; - - SingleSelectTypeOptionBloc( - TypeOptionContext typeOptionContext, - ) : service = TypeOptionService(gridId: typeOptionContext.gridId, fieldId: typeOptionContext.field.id), - super( - SingleSelectTypeOptionState.initial(SingleSelectTypeOption.fromBuffer(typeOptionContext.data)), - ) { - on( - (event, emit) async { - await event.map( - createOption: (_CreateOption value) async { - final result = await service.newOption(name: value.optionName); - result.fold( - (option) { - emit(state.copyWith(typeOption: _insertOption(option))); - }, - (err) => Log.error(err), - ); - }, - updateOption: (_UpdateOption value) async { - emit(state.copyWith(typeOption: _updateOption(value.option))); - }, - deleteOption: (_DeleteOption value) { - emit(state.copyWith(typeOption: _deleteOption(value.option))); - }, - ); - }, - ); - } - - @override - Future close() async { - return super.close(); - } - - SingleSelectTypeOption _insertOption(SelectOption option) { - state.typeOption.freeze(); - return state.typeOption.rebuild((typeOption) { - typeOption.options.insert(0, option); - }); - } - - SingleSelectTypeOption _updateOption(SelectOption option) { - state.typeOption.freeze(); - return state.typeOption.rebuild((typeOption) { - final index = typeOption.options.indexWhere((element) => element.id == option.id); - if (index != -1) { - typeOption.options[index] = option; - } - }); - } - - SingleSelectTypeOption _deleteOption(SelectOption option) { - state.typeOption.freeze(); - return state.typeOption.rebuild((typeOption) { - final index = typeOption.options.indexWhere((element) => element.id == option.id); - if (index != -1) { - typeOption.options.removeAt(index); - } - }); - } -} - -@freezed -class SingleSelectTypeOptionEvent with _$SingleSelectTypeOptionEvent { - const factory SingleSelectTypeOptionEvent.createOption(String optionName) = _CreateOption; - const factory SingleSelectTypeOptionEvent.updateOption(SelectOption option) = _UpdateOption; - const factory SingleSelectTypeOptionEvent.deleteOption(SelectOption option) = _DeleteOption; -} - -@freezed -class SingleSelectTypeOptionState with _$SingleSelectTypeOptionState { - const factory SingleSelectTypeOptionState({ - required SingleSelectTypeOption typeOption, - }) = _SingleSelectTypeOptionState; - - factory SingleSelectTypeOptionState.initial(SingleSelectTypeOption typeOption) => SingleSelectTypeOptionState( - typeOption: typeOption, - ); -} diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart new file mode 100644 index 0000000000..91d8de7a28 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart @@ -0,0 +1,78 @@ +import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; +import 'package:flowy_sdk/log.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'dart:async'; +import 'package:protobuf/protobuf.dart'; +import 'select_option_type_option_bloc.dart'; +import 'type_option_service.dart'; + +class SingleSelectTypeOptionContext extends TypeOptionContext + with SelectOptionTypeOptionAction { + final TypeOptionService service; + + SingleSelectTypeOptionContext({ + required SingleSelectTypeOptionDataBuilder dataBuilder, + required GridFieldContext fieldContext, + }) : service = TypeOptionService( + gridId: fieldContext.gridId, + fieldId: fieldContext.field.id, + ), + super(dataBuilder: dataBuilder, fieldContext: fieldContext); + + @override + List Function(SelectOption) get deleteOption { + return (SelectOption option) { + typeOption.freeze(); + typeOption = typeOption.rebuild((typeOption) { + final index = typeOption.options.indexWhere((element) => element.id == option.id); + if (index != -1) { + typeOption.options.removeAt(index); + } + }); + return typeOption.options; + }; + } + + @override + Future> Function(String) get insertOption { + return (String optionName) { + return service.newOption(name: optionName).then((result) { + return result.fold( + (option) { + typeOption.freeze(); + typeOption = typeOption.rebuild((typeOption) { + typeOption.options.insert(0, option); + }); + + return typeOption.options; + }, + (err) { + Log.error(err); + return typeOption.options; + }, + ); + }); + }; + } + + @override + List Function(SelectOption) get udpateOption { + return (SelectOption option) { + typeOption.freeze(); + typeOption = typeOption.rebuild((typeOption) { + final index = typeOption.options.indexWhere((element) => element.id == option.id); + if (index != -1) { + typeOption.options[index] = option; + } + }); + return typeOption.options; + }; + } +} + +class SingleSelectTypeOptionDataBuilder extends TypeOptionDataBuilder { + @override + SingleSelectTypeOption fromBuffer(List buffer) { + return SingleSelectTypeOption.fromBuffer(buffer); + } +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart index 92f2263c5d..4bf61cc1ff 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart @@ -1,5 +1,6 @@ import 'dart:typed_data'; +import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; @@ -7,6 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:protobuf/protobuf.dart'; class TypeOptionService { final String gridId; @@ -32,13 +34,76 @@ class TypeOptionService { } } -class TypeOptionContext { +abstract class TypeOptionDataBuilder { + T fromBuffer(List buffer); +} + +class TypeOptionContext { + T? _typeOptionObject; + final GridFieldContext _fieldContext; + final TypeOptionDataBuilder dataBuilder; + + TypeOptionContext({ + required this.dataBuilder, + required GridFieldContext fieldContext, + }) : _fieldContext = fieldContext; + + String get gridId => _fieldContext.gridId; + + Field get field => _fieldContext.field; + + T get typeOption { + if (_typeOptionObject != null) { + return _typeOptionObject!; + } + + final T object = dataBuilder.fromBuffer(_fieldContext.typeOptionData); + _typeOptionObject = object; + return object; + } + + set typeOption(T typeOption) { + _fieldContext.typeOptionData = typeOption.writeToBuffer(); + _typeOptionObject = typeOption; + } +} + +abstract class TypeOptionFieldDelegate { + void onFieldChanged(void Function(String) callback); + void dispose(); +} + +class TypeOptionContext2 { final String gridId; final Field field; - final Uint8List data; - const TypeOptionContext({ + final FieldService _fieldService; + T? _data; + final TypeOptionDataBuilder dataBuilder; + + TypeOptionContext2({ required this.gridId, required this.field, - required this.data, - }); + required this.dataBuilder, + Uint8List? data, + }) : _fieldService = FieldService(gridId: gridId, fieldId: field.id) { + if (data != null) { + _data = dataBuilder.fromBuffer(data); + } + } + + Future> typeOptionData() { + if (_data != null) { + return Future(() => left(_data!)); + } + + return _fieldService.getFieldTypeOptionData(fieldType: field.fieldType).then((result) { + return result.fold( + (data) { + _data = dataBuilder.fromBuffer(data.typeOptionData); + return left(_data!); + }, + (err) => right(err), + ); + }); + } } 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 7f35d50978..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.clonedFields, + 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/grid_header_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart index 3195715413..526dda9085 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart @@ -15,7 +15,7 @@ class GridHeaderBloc extends Bloc { GridHeaderBloc({ required this.gridId, required this.fieldCache, - }) : super(GridHeaderState.initial(fieldCache.clonedFields)) { + }) : super(GridHeaderState.initial(fieldCache.fields)) { on( (event, emit) async { await event.map( diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart index 98827e6b72..38ebfc63fc 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; @@ -6,8 +8,6 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - import 'cell/cell_service/cell_service.dart'; import 'row/row_service.dart'; @@ -59,7 +59,7 @@ typedef ChangesetListener = void Function(GridFieldChangeset); class GridFieldCache { final String gridId; late final GridFieldsListener _fieldListener; - final FieldsNotifier _fieldNotifier = FieldsNotifier(); + FieldsNotifier? _fieldNotifier = FieldsNotifier(); final List _changesetListener = []; GridFieldCache({required this.gridId}) { @@ -81,15 +81,16 @@ class GridFieldCache { Future dispose() async { await _fieldListener.stop(); - _fieldNotifier.dispose(); + _fieldNotifier?.dispose(); + _fieldNotifier = null; } - UnmodifiableListView get unmodifiableFields => UnmodifiableListView(_fieldNotifier.fields); + UnmodifiableListView get unmodifiableFields => UnmodifiableListView(_fieldNotifier?.fields ?? []); - List get clonedFields => [..._fieldNotifier.fields]; + List get fields => [..._fieldNotifier?.fields ?? []]; set fields(List fields) { - _fieldNotifier.fields = [...fields]; + _fieldNotifier?.fields = [...fields]; } VoidCallback addListener( @@ -100,7 +101,7 @@ class GridFieldCache { } if (onChanged != null) { - onChanged(clonedFields); + onChanged(fields); } if (listener != null) { @@ -108,12 +109,12 @@ class GridFieldCache { } } - _fieldNotifier.addListener(f); + _fieldNotifier?.addListener(f); return f; } void removeListener(VoidCallback f) { - _fieldNotifier.removeListener(f); + _fieldNotifier?.removeListener(f); } void addChangesetListener(ChangesetListener listener) { @@ -131,43 +132,43 @@ class GridFieldCache { if (deletedFields.isEmpty) { return; } - final List fields = _fieldNotifier.fields; + final List newFields = fields; final Map deletedFieldMap = { for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder }; - fields.retainWhere((field) => (deletedFieldMap[field.id] == null)); - _fieldNotifier.fields = fields; + newFields.retainWhere((field) => (deletedFieldMap[field.id] == null)); + _fieldNotifier?.fields = newFields; } void _insertFields(List insertedFields) { if (insertedFields.isEmpty) { return; } - final List fields = _fieldNotifier.fields; + final List newFields = fields; for (final indexField in insertedFields) { - if (fields.length > indexField.index) { - fields.insert(indexField.index, indexField.field_1); + if (newFields.length > indexField.index) { + newFields.insert(indexField.index, indexField.field_1); } else { - fields.add(indexField.field_1); + newFields.add(indexField.field_1); } } - _fieldNotifier.fields = fields; + _fieldNotifier?.fields = newFields; } void _updateFields(List updatedFields) { if (updatedFields.isEmpty) { return; } - final List fields = _fieldNotifier.fields; + final List newFields = fields; for (final updatedField in updatedFields) { - final index = fields.indexWhere((field) => field.id == updatedField.id); + final index = newFields.indexWhere((field) => field.id == updatedField.id); if (index != -1) { - fields.removeAt(index); - fields.insert(index, updatedField); + newFields.removeAt(index); + newFields.insert(index, updatedField); } } - _fieldNotifier.fields = fields; + _fieldNotifier?.fields = newFields; } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/prelude.dart b/frontend/app_flowy/lib/workspace/application/grid/prelude.dart index e06f5d9da8..6ea198f303 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/prelude.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/prelude.dart @@ -13,12 +13,12 @@ export 'field/field_editor_pannel_bloc.dart'; // Field Type Option export 'field/type_option/date_bloc.dart'; export 'field/type_option/number_bloc.dart'; -export 'field/type_option/single_select_bloc.dart'; +export 'field/type_option/single_select_type_option.dart'; // Cell export 'cell/text_cell_bloc.dart'; export 'cell/number_cell_bloc.dart'; -export 'cell/selection_cell_bloc.dart'; +export 'cell/select_option_cell_bloc.dart'; export 'cell/date_cell_bloc.dart'; export 'cell/checkbox_cell_bloc.dart'; export 'cell/cell_service/cell_service.dart'; 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/application/grid/setting/property_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart index 42d2327f5b..a88423e56d 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart @@ -14,7 +14,7 @@ class GridPropertyBloc extends Bloc { GridPropertyBloc({required String gridId, required GridFieldCache fieldCache}) : _fieldCache = fieldCache, - super(GridPropertyState.initial(gridId, fieldCache.clonedFields)) { + super(GridPropertyState.initial(gridId, fieldCache.fields)) { on( (event, emit) async { await event.map( diff --git a/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart b/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart index 796a0357b9..f3d9930842 100644 --- a/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart @@ -49,6 +49,9 @@ class HomeBloc extends Bloc { unauthorized: (_Unauthorized value) { emit(state.copyWith(unauthorized: true)); }, + collapseMenu: (e) { + emit(state.copyWith(isMenuCollapsed: !state.isMenuCollapsed)); + }, ); }); } @@ -77,6 +80,7 @@ class HomeEvent with _$HomeEvent { const factory HomeEvent.dismissEditPannel() = _DismissEditPannel; const factory HomeEvent.didReceiveWorkspaceSetting(CurrentWorkspaceSetting setting) = _DidReceiveWorkspaceSetting; const factory HomeEvent.unauthorized(String msg) = _Unauthorized; + const factory HomeEvent.collapseMenu() = _CollapseMenu; } @freezed @@ -87,6 +91,7 @@ class HomeState with _$HomeState { required Option pannelContext, required CurrentWorkspaceSetting workspaceSetting, required bool unauthorized, + required bool isMenuCollapsed, }) = _HomeState; factory HomeState.initial(CurrentWorkspaceSetting workspaceSetting) => HomeState( @@ -95,5 +100,6 @@ class HomeState with _$HomeState { pannelContext: none(), workspaceSetting: workspaceSetting, unauthorized: false, + isMenuCollapsed: false, ); } diff --git a/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart b/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart index a2c167cde4..db8f2c534b 100644 --- a/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart @@ -25,10 +25,6 @@ class MenuBloc extends Bloc { listener.start(addAppCallback: _handleAppsOrFail); await _fetchApps(emit); }, - collapse: (e) async { - final isCollapse = state.isCollapse; - emit(state.copyWith(isCollapse: !isCollapse)); - }, openPage: (e) async { emit(state.copyWith(plugin: e.plugin)); }, @@ -94,7 +90,6 @@ class MenuBloc extends Bloc { @freezed class MenuEvent with _$MenuEvent { const factory MenuEvent.initial() = _Initial; - const factory MenuEvent.collapse() = _Collapse; const factory MenuEvent.openPage(Plugin plugin) = _OpenPage; const factory MenuEvent.createApp(String name, {String? desc}) = _CreateApp; const factory MenuEvent.moveApp(int fromIndex, int toIndex) = _MoveApp; @@ -104,14 +99,12 @@ class MenuEvent with _$MenuEvent { @freezed class MenuState with _$MenuState { const factory MenuState({ - required bool isCollapse, required List apps, required Either successOrFailure, required Plugin plugin, }) = _MenuState; factory MenuState.initial() => MenuState( - isCollapse: false, apps: [], successOrFailure: left(unit), plugin: makePlugin(pluginType: DefaultPlugin.blank.type()), diff --git a/frontend/app_flowy/lib/workspace/presentation/home/home_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 65e315f56d..07bd99deb1 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart @@ -1,27 +1,26 @@ +import 'dart:io' show Platform; + import 'package:app_flowy/startup/startup.dart'; -import 'package:app_flowy/workspace/presentation/home/home_screen.dart'; +import 'package:app_flowy/workspace/application/home/home_bloc.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'; import 'package:app_flowy/workspace/presentation/home/navigation.dart'; +import 'package:app_flowy/core/frameless_window.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/style_widget/extension.dart'; import 'package:flowy_infra/notifier.dart'; 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 @@ -69,8 +68,7 @@ class _FadingIndexedStackState extends State { @override void initState() { super.initState(); - fToast = FToast(); - fToast.init(HomeScreen.scaffoldKey.currentState!.context); + initToastWithContext(context); } @override @@ -152,7 +150,7 @@ class HomeStackManager { child: Selector( selector: (context, notifier) => notifier.titleWidget, builder: (context, widget, child) { - return const HomeTopBar(); + return const MoveWindowDetector(child: HomeTopBar()); }, ), ); @@ -191,6 +189,14 @@ class HomeTopBar extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ + BlocBuilder( + buildWhen: ((previous, current) => previous.isMenuCollapsed != current.isMenuCollapsed), + builder: (context, state) { + if (state.isMenuCollapsed && Platform.isMacOS) { + return const HSpace(80); + } + return const HSpace(0); + }), const FlowyNavigation(), const HSpace(16), ChangeNotifierProvider.value( diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/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/menu/app/section/section.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart index d9fd618e9a..d352db6620 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart @@ -26,7 +26,7 @@ class ViewSection extends StatelessWidget { listenWhen: (p, c) => p.selectedView != c.selectedView, listener: (context, state) { if (state.selectedView != null) { - WidgetsBinding.instance?.addPostFrameCallback((_) { + WidgetsBinding.instance.addPostFrameCallback((_) { getIt().setPlugin(state.selectedView!.plugin()); }); } diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart index b888ab7631..0eb22e3d1f 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart @@ -1,6 +1,8 @@ export './app/header/header.dart'; export './app/menu_app.dart'; +import 'dart:io' show Platform; +import 'package:app_flowy/workspace/presentation/home/home_sizes.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:app_flowy/workspace/presentation/plugins/trash/menu.dart'; import 'package:flowy_infra/notifier.dart'; @@ -18,7 +20,9 @@ import 'package:expandable/expandable.dart'; import 'package:flowy_infra/time/duration.dart'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/menu/menu_bloc.dart'; -import 'package:app_flowy/workspace/presentation/home/home_sizes.dart'; +import 'package:app_flowy/workspace/application/home/home_bloc.dart'; +import 'package:app_flowy/core/frameless_window.dart'; +// import 'package:app_flowy/workspace/presentation/home/home_sizes.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; @@ -59,10 +63,10 @@ class HomeMenu extends StatelessWidget { getIt().setPlugin(state.plugin); }, ), - BlocListener( - listenWhen: (p, c) => p.isCollapse != c.isCollapse, + BlocListener( + listenWhen: (p, c) => p.isMenuCollapsed != c.isMenuCollapsed, listener: (context, state) { - _collapsedNotifier.value = state.isCollapse; + _collapsedNotifier.value = state.isMenuCollapsed; }, ) ], @@ -179,6 +183,17 @@ class MenuSharedState { class MenuTopBar extends StatelessWidget { const MenuTopBar({Key? key}) : super(key: key); + + Widget renderIcon(BuildContext context) { + if (Platform.isMacOS) { + return Container(); + } + final theme = context.watch(); + return (theme.isDark + ? svgWithSize("flowy_logo_dark_mode", const Size(92, 17)) + : svgWithSize("flowy_logo_with_text", const Size(92, 17))); + } + @override Widget build(BuildContext context) { final theme = context.watch(); @@ -186,20 +201,19 @@ class MenuTopBar extends StatelessWidget { builder: (context, state) { return SizedBox( height: HomeSizes.topBarHeight, - child: Row( + child: MoveWindowDetector( + child: Row( children: [ - (theme.isDark - ? svgWithSize("flowy_logo_dark_mode", const Size(92, 17)) - : svgWithSize("flowy_logo_with_text", const Size(92, 17))), + renderIcon(context), const Spacer(), FlowyIconButton( width: 28, - onPressed: () => context.read().add(const MenuEvent.collapse()), + onPressed: () => context.read().add(const HomeEvent.collapseMenu()), iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), icon: svgWidget("home/hide_menu", color: theme.iconColor), ) ], - ), + )), ); }, ); diff --git a/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart b/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart index b6947c79fe..f52b7224f6 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart @@ -1,3 +1,4 @@ +import 'package:app_flowy/workspace/application/home/home_bloc.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/notifier.dart'; @@ -95,6 +96,7 @@ class FlowyNavigation extends StatelessWidget { width: 24, onPressed: () { notifier.value = false; + ctx.read().add(const HomeEvent.collapseMenu()); }, iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2), icon: svgWidget("home/hide_menu", color: theme.iconColor), diff --git a/frontend/app_flowy/lib/workspace/presentation/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/document.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/doc/document.dart index f785dd9ad1..111e103796 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/doc/document.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/doc/document.dart @@ -7,6 +7,7 @@ import 'package:app_flowy/workspace/application/appearance.dart'; import 'package:app_flowy/workspace/application/doc/share_bloc.dart'; import 'package:app_flowy/workspace/application/view/view_listener.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/plugins/widgets/left_bar_item.dart'; import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart'; import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart'; @@ -179,6 +180,7 @@ class DocumentShareButton extends StatelessWidget { switch (action) { case ShareAction.markdown: context.read().add(const DocShareEvent.shareMarkdown()); + showMessageToast('Exported to: ${LocaleKeys.notifications_export_path.tr()}'); break; case ShareAction.copyLink: FlowyAlertDialog(title: LocaleKeys.shareAction_workInProgress.tr()).show(context); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/tool_bar.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/tool_bar.dart index 2ab0b78cc9..8dae41f986 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/tool_bar.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/widget/toolbar/tool_bar.dart @@ -184,7 +184,7 @@ class _ToolbarButtonListState extends State with WidgetsBindi // Listening to the WidgetsBinding instance is necessary so that we can // hide the arrows when the window gets a new size and thus the toolbar // becomes scrollable/unscrollable. - WidgetsBinding.instance!.addObserver(this); + WidgetsBinding.instance.addObserver(this); // Workaround to allow the scroll controller attach to our ListView so that // we can detect if overflow arrows need to be shown on init. @@ -226,7 +226,7 @@ class _ToolbarButtonListState extends State with WidgetsBindi @override void dispose() { _controller.dispose(); - WidgetsBinding.instance!.removeObserver(this); + WidgetsBinding.instance.removeObserver(this); super.dispose(); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/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 7d21cb7e9a..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,18 +1,16 @@ 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'; -import 'selection_cell/selection_cell.dart'; +import 'select_option_cell/select_option_cell.dart'; import 'text_cell.dart'; +import 'url_cell/url_cell.dart'; GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) { final key = ValueKey(gridCell.cellId()); @@ -32,10 +30,10 @@ GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, { return NumberCell(cellContextBuilder: cellContextBuilder, key: key); case FieldType.RichText: return GridTextCell(cellContextBuilder: cellContextBuilder, style: style, key: key); - - default: - throw UnimplementedError; + case FieldType.URL: + return GridURLCell(cellContextBuilder: cellContextBuilder, style: style, key: key); } + throw UnimplementedError; } class BlankCell extends StatelessWidget { @@ -47,26 +45,132 @@ class BlankCell extends StatelessWidget { } } -abstract class GridCellWidget extends HoverWidget { - @override - final ValueNotifier onFocus = ValueNotifier(false); +abstract class CellEditable { + GridCellFocusListener get beginFocus; - final GridCellRequestFocusNotifier requestFocus = GridCellRequestFocusNotifier(); + ValueNotifier get onCellFocus; - GridCellWidget({Key? key}) : super(key: key); + 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() { @@ -82,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!); } @@ -94,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 c4da2a223f..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 @@ -14,17 +14,16 @@ class CheckboxCell extends 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(); } @@ -41,7 +40,7 @@ class _CheckboxCellState extends State { onPressed: () => context.read().add(const CheckboxCellEvent.select()), iconPadding: EdgeInsets.zero, icon: icon, - width: 23, + width: 20, ), ); }, @@ -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 d1d15ea4b3..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 @@ -5,7 +5,7 @@ import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/grid/prelude.dart'; import '../cell_builder.dart'; -import 'calendar.dart'; +import 'date_editor.dart'; class DateCellStyle extends GridCellStyle { Alignment alignment; @@ -35,10 +35,10 @@ class DateCell extends 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 = CellCalendar(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/calendar.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart similarity index 94% rename from frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/calendar.dart rename to frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart index c19e8c3001..93d304cd1a 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/calendar.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart @@ -22,10 +22,10 @@ final kFirstDay = DateTime(kToday.year, kToday.month - 3, kToday.day); final kLastDay = DateTime(kToday.year, kToday.month + 3, kToday.day); const kMargin = EdgeInsets.symmetric(horizontal: 6, vertical: 10); -class CellCalendar with FlowyOverlayDelegate { +class DateCellEditor with FlowyOverlayDelegate { final VoidCallback onDismissed; - const CellCalendar({ + const DateCellEditor({ required this.onDismissed, }); @@ -33,23 +33,14 @@ class CellCalendar with FlowyOverlayDelegate { BuildContext context, { required GridDateCellContext cellContext, }) async { - CellCalendar.remove(context); + DateCellEditor.remove(context); final result = await cellContext.getTypeOptionData(); result.fold( (data) { - final typeOptionData = DateTypeOption.fromBuffer(data); - // DateTime? selectedDay; - // final cellData = cellContext.getCellData(); - - // if (cellData != null) { - // final timestamp = $fixnum.Int64.parseInt(cellData).toInt(); - // selectedDay = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); - // } - final calendar = _CellCalendarWidget( cellContext: cellContext, - dateTypeOption: typeOptionData, + dateTypeOption: DateTypeOption.fromBuffer(data.typeOptionData), ); FlowyOverlay.of(context).insertWithAnchor( @@ -57,7 +48,7 @@ class CellCalendar with FlowyOverlayDelegate { child: calendar, constraints: BoxConstraints.loose(const Size(320, 500)), ), - identifier: CellCalendar.identifier(), + identifier: DateCellEditor.identifier(), anchorContext: context, anchorDirection: AnchorDirection.leftWithCenterAligned, style: FlowyOverlayStyle(blur: false), @@ -73,7 +64,7 @@ class CellCalendar with FlowyOverlayDelegate { } static String identifier() { - return (CellCalendar).toString(); + return (DateCellEditor).toString(); } @override @@ -169,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)); }, ); @@ -243,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)); } }); @@ -266,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, @@ -335,6 +331,7 @@ class _CalDateTimeSetting extends StatefulWidget { } void show(BuildContext context) { + hide(context); FlowyOverlay.of(context).insertWithAnchor( widget: OverlayContainer( child: this, @@ -346,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 c0b3427e65..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'; @@ -16,101 +15,79 @@ class NumberCell extends 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/prelude.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart index 3c7e43ecc0..792e58810f 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart @@ -3,4 +3,4 @@ export 'text_cell.dart'; export 'number_cell.dart'; export 'date_cell/date_cell.dart'; export 'checkbox_cell.dart'; -export 'selection_cell/selection_cell.dart'; +export 'select_option_cell/select_option_cell.dart'; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart similarity index 63% rename from frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart rename to frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart index c4140ad216..27e36ad46d 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart @@ -1,4 +1,5 @@ import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flutter/material.dart'; @@ -63,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); @@ -73,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, ); } @@ -86,23 +91,63 @@ class SelectOptionTag extends StatelessWidget { Widget build(BuildContext context) { return ChoiceChip( pressElevation: 1, - label: FlowyText.medium(name, fontSize: 12), + label: FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis), selectedColor: color, backgroundColor: color, labelPadding: const EdgeInsets.symmetric(horizontal: 6), selected: true, - onSelected: (_) {}, + onSelected: (_) { + if (onSelected != null) { + onSelected!(); + } + }, + ); + } +} + +class SelectOptionTagCell extends StatelessWidget { + final List children; + final void Function(SelectOption) onSelected; + final SelectOption option; + const SelectOptionTagCell({ + required this.option, + required this.onSelected, + this.children = const [], + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return Stack( + fit: StackFit.expand, + children: [ + FlowyHover( + style: HoverStyle(hoverColor: theme.hover), + child: InkWell( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 3), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + fit: FlexFit.loose, + flex: 2, + child: SelectOptionTag.fromSelectOption( + context: context, + option: option, + onSelected: () => onSelected(option), + ), + ), + const Spacer(), + ...children, + ], + ), + ), + onTap: () => onSelected(option), + ), + ), + ], ); - - // 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), - // ); } } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart similarity index 87% rename from frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart rename to frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart index 0c754b0297..e878ac2c58 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart @@ -10,7 +10,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'extension.dart'; -import 'selection_editor.dart'; +import 'select_option_editor.dart'; class SelectOptionCellStyle extends GridCellStyle { String placeholder; @@ -41,12 +41,12 @@ class SingleSelectCell extends GridCellWidget { } class _SingleSelectCellState extends State { - late SelectionCellBloc _cellBloc; + late SelectOptionCellBloc _cellBloc; @override void initState() { final cellContext = widget.cellContextBuilder.build() as GridSelectOptionCellContext; - _cellBloc = getIt(param1: cellContext)..add(const SelectionCellEvent.initial()); + _cellBloc = getIt(param1: cellContext)..add(const SelectOptionCellEvent.initial()); super.initState(); } @@ -54,12 +54,12 @@ class _SingleSelectCellState extends State { Widget build(BuildContext context) { return BlocProvider.value( value: _cellBloc, - child: BlocBuilder( + child: BlocBuilder( builder: (context, state) { return _SelectOptionCell( selectOptions: state.selectedOptions, cellStyle: widget.cellStyle, - onFocus: (value) => widget.onFocus.value = value, + onFocus: (value) => widget.onCellEditing.value = value, cellContextBuilder: widget.cellContextBuilder); }, ), @@ -95,12 +95,12 @@ class MultiSelectCell extends GridCellWidget { } class _MultiSelectCellState extends State { - late SelectionCellBloc _cellBloc; + late SelectOptionCellBloc _cellBloc; @override void initState() { final cellContext = widget.cellContextBuilder.build() as GridSelectOptionCellContext; - _cellBloc = getIt(param1: cellContext)..add(const SelectionCellEvent.initial()); + _cellBloc = getIt(param1: cellContext)..add(const SelectOptionCellEvent.initial()); super.initState(); } @@ -108,12 +108,12 @@ class _MultiSelectCellState extends State { Widget build(BuildContext context) { return BlocProvider.value( value: _cellBloc, - child: BlocBuilder( + child: BlocBuilder( builder: (context, state) { return _SelectOptionCell( selectOptions: state.selectedOptions, cellStyle: widget.cellStyle, - onFocus: (value) => widget.onFocus.value = value, + onFocus: (value) => widget.onCellEditing.value = value, cellContextBuilder: widget.cellContextBuilder); }, ), @@ -160,7 +160,7 @@ class _SelectOptionCell extends StatelessWidget { .toList(); child = Align( alignment: Alignment.centerLeft, - child: Wrap(children: tags, spacing: 4, runSpacing: 4), + child: Wrap(children: tags, spacing: 4, runSpacing: 2), ); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart similarity index 80% rename from frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart rename to frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart index 6e96fccbfb..51c550ccb2 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart @@ -1,13 +1,12 @@ import 'dart:collection'; import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; -import 'package:app_flowy/workspace/application/grid/cell/selection_editor_bloc.dart'; +import 'package:app_flowy/workspace/application/grid/cell/select_option_editor_bloc.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart'; 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_list.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; @@ -37,10 +36,10 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => SelectOptionEditorBloc( + create: (context) => SelectOptionCellEditorBloc( cellContext: cellContext, )..add(const SelectOptionEditorEvent.initial()), - child: BlocBuilder( + child: BlocBuilder( builder: (context, state) { return CustomScrollView( shrinkWrap: true, @@ -102,7 +101,7 @@ class _OptionList extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( builder: (context, state) { List cells = []; cells.addAll(state.options.map((option) { @@ -145,7 +144,7 @@ class _TextField extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( builder: (context, state) { final optionMap = LinkedHashMap.fromIterable(state.selectedOptions, key: (option) => option.name, value: (option) => option); @@ -157,11 +156,12 @@ 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)); + context.read().add(SelectOptionEditorEvent.filterOption(text)); }, onNewTag: (tagName) { - context.read().add(SelectOptionEditorEvent.newOption(tagName)); + context.read().add(SelectOptionEditorEvent.newOption(tagName)); }, ), ); @@ -208,6 +208,7 @@ class _CreateOptionCell extends StatelessWidget { SelectOptionTag( name: name, color: theme.shader6, + onSelected: () => context.read().add(SelectOptionEditorEvent.newOption(name)), ), ], ); @@ -224,63 +225,47 @@ class _SelectOptionCell extends StatelessWidget { final theme = context.watch(); return SizedBox( height: GridSize.typeOptionItemHeight, - child: Stack( - fit: StackFit.expand, + child: Row( children: [ - _body(theme, context), - InkWell( - onTap: () { - context.read().add(SelectOptionEditorEvent.selectOption(option.id)); - }, + Flexible( + fit: FlexFit.loose, + child: SelectOptionTagCell( + option: option, + onSelected: (option) { + context.read().add(SelectOptionEditorEvent.selectOption(option.id)); + }, + children: [ + if (isSelected) + Padding( + padding: const EdgeInsets.only(right: 6), + child: svgWidget("grid/checkmark"), + ), + ], + ), ), + FlowyIconButton( + width: 30, + onPressed: () => _showEditPannel(context), + iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), + icon: svgWidget("editor/details", color: theme.iconColor), + ) ], ), ); } - FlowyHover _body(AppTheme theme, BuildContext context) { - return FlowyHover( - style: HoverStyle(hoverColor: theme.hover), - builder: (_, onHover) { - List children = [ - SelectOptionTag( - name: option.name, - color: option.color.make(context), - isSelected: isSelected, - ), - const Spacer(), - ]; - - if (isSelected) { - children.add(svgWidget("grid/checkmark")); - } - - if (onHover) { - children.add(FlowyIconButton( - width: 30, - onPressed: () => _showEditPannel(context), - iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), - icon: svgWidget("editor/details", color: theme.iconColor), - )); - } - - return Row(children: children); - }, - ); - } - void _showEditPannel(BuildContext context) { - final pannel = EditSelectOptionPannel( + final pannel = SelectOptionTypeOptionEditor( option: option, onDeleted: () { - context.read().add(SelectOptionEditorEvent.deleteOption(option)); + context.read().add(SelectOptionEditorEvent.deleteOption(option)); }, onUpdated: (updatedOption) { - context.read().add(SelectOptionEditorEvent.updateOption(updatedOption)); + context.read().add(SelectOptionEditorEvent.updateOption(updatedOption)); }, key: ValueKey(option.id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value. ); - final overlayIdentifier = (EditSelectOptionPannel).toString(); + final overlayIdentifier = (SelectOptionTypeOptionEditor).toString(); FlowyOverlay.of(context).remove(overlayIdentifier); FlowyOverlay.of(context).insertWithAnchor( diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/text_field.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart similarity index 97% rename from frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/text_field.dart rename to 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/selection_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 14ace02d28..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 @@ -29,13 +29,12 @@ class GridTextCell extends 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 new file mode 100644 index 0000000000..f4da18be86 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart @@ -0,0 +1,113 @@ +import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; +import 'package:app_flowy/workspace/application/grid/cell/url_cell_editor_bloc.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter_bloc/flutter_bloc.dart'; + +class URLCellEditor extends StatefulWidget with FlowyOverlayDelegate { + final GridURLCellContext cellContext; + final VoidCallback completed; + const URLCellEditor({required this.cellContext, required this.completed, Key? key}) : super(key: key); + + @override + State createState() => _URLCellEditorState(); + + 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: 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 { + late URLCellEditorBloc _cellBloc; + late TextEditingController _controller; + + @override + void initState() { + _cellBloc = URLCellEditorBloc(cellContext: widget.cellContext); + _cellBloc.add(const URLCellEditorEvent.initial()); + _controller = TextEditingController(text: _cellBloc.state.content); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider.value( + value: _cellBloc, + child: BlocListener( + listener: (context, state) { + if (_controller.text != state.content) { + _controller.text = state.content; + } + }, + child: TextField( + autofocus: true, + controller: _controller, + onChanged: (value) => focusChanged(), + maxLines: null, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + decoration: const InputDecoration( + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + hintText: "", + isDense: true, + ), + ), + ), + ); + } + + @override + Future dispose() async { + _cellBloc.close(); + + super.dispose(); + } + + Future focusChanged() async { + if (mounted) { + if (_cellBloc.isClosed == false && _controller.text != _cellBloc.state.content) { + _cellBloc.add(URLCellEditorEvent.updateText(_controller.text)); + } + } + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart new file mode 100644 index 0000000000..e37dca6632 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart @@ -0,0 +1,194 @@ +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: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'; +import '../cell_builder.dart'; +import 'cell_editor.dart'; + +class GridURLCellStyle extends GridCellStyle { + String? placeholder; + + List accessoryTypes; + + GridURLCellStyle({ + this.placeholder, + this.accessoryTypes = const [], + }); +} + +enum GridURLCellAccessoryType { + edit, + copyURL, +} + +class GridURLCell extends GridCellWidget { + final GridCellContextBuilder cellContextBuilder; + late final GridURLCellStyle? cellStyle; + GridURLCell({ + required this.cellContextBuilder, + GridCellStyle? style, + Key? key, + }) : super(key: key) { + if (style != null) { + cellStyle = (style as GridURLCellStyle); + } else { + cellStyle = null; + } + } + + @override + 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 GridCellState { + late URLCellBloc _cellBloc; + + @override + void initState() { + final cellContext = widget.cellContextBuilder.build() as GridURLCellContext; + _cellBloc = URLCellBloc(cellContext: cellContext); + _cellBloc.add(const URLCellEvent.initial()); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return BlocProvider.value( + value: _cellBloc, + child: BlocBuilder( + builder: (context, state) { + final richText = RichText( + textAlign: TextAlign.left, + text: TextSpan( + text: state.content, + style: TextStyle( + color: theme.main2, + fontSize: 14, + decoration: TextDecoration.underline, + ), + ), + ); + + return SizedBox.expand( + child: GestureDetector( + child: Align(alignment: Alignment.centerLeft, child: richText), + onTap: () async { + final url = context.read().state.url; + await _openUrlOrEdit(url); + }, + )); + }, + ), + ); + } + + @override + Future dispose() async { + _cellBloc.close(); + super.dispose(); + } + + 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; + widget.onCellEditing.value = true; + URLCellEditor.show(context, cellContext, () { + widget.onCellEditing.value = false; + }); + } + } + + @override + void requestBeginFocus() { + _openUrlOrEdit(_cellBloc.state.url); + } + + @override + String? onCopy() => _cellBloc.state.content; + + @override + void onInsert(String value) { + _cellBloc.add(URLCellEvent.updateURL(value)); + } +} + +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 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/common/text_field.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart index b5860b1fbb..43d394bfe1 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart @@ -7,6 +7,7 @@ class InputTextField extends StatefulWidget { final void Function(String)? onDone; final void Function(String)? onChanged; final void Function() onCanceled; + final bool autoClearWhenDone; final String text; const InputTextField({ @@ -14,6 +15,7 @@ class InputTextField extends StatefulWidget { this.onDone, required this.onCanceled, this.onChanged, + this.autoClearWhenDone = false, Key? key, }) : super(key: key); @@ -57,6 +59,10 @@ class _InputTextFieldState extends State { if (widget.onDone != null) { widget.onDone!(_controller.text); } + + if (widget.autoClearWhenDone) { + _controller.text = ""; + } }, ); } 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 eb264e6664..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, @@ -37,8 +37,8 @@ class GridFieldCell extends StatelessWidget { child: _DragToExpandLine(), ); - return _CellContainer( - width: state.field.width.toDouble(), + return _GridHeaderCellContainer( + width: state.width, child: Stack( alignment: Alignment.centerRight, fit: StackFit.expand, @@ -60,21 +60,23 @@ class GridFieldCell extends StatelessWidget { void _showFieldEditor(BuildContext context) { final state = context.read().state; + final field = state.field; FieldEditor( gridId: state.gridId, - fieldContextLoader: FieldContextLoaderAdaptor( + fieldName: field.name, + contextLoader: FieldContextLoader( gridId: state.gridId, - field: state.field, + field: field, ), ).show(context); } } -class _CellContainer extends StatelessWidget { +class _GridHeaderCellContainer extends StatelessWidget { final Widget child; final double width; - const _CellContainer({ + const _GridHeaderCellContainer({ required this.child, required this.width, Key? key, @@ -83,7 +85,7 @@ class _CellContainer 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, @@ -112,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/field_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart index 11e55e0e06..3390351663 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart @@ -1,4 +1,3 @@ -import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/grid/field/field_editor_bloc.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -11,16 +10,42 @@ import 'package:app_flowy/generated/locale_keys.g.dart'; import 'field_name_input.dart'; import 'field_editor_pannel.dart'; -class FieldEditor extends FlowyOverlayDelegate { +class FieldEditor extends StatelessWidget with FlowyOverlayDelegate { final String gridId; - final FieldEditorBloc _fieldEditorBloc; - final EditFieldContextLoader fieldContextLoader; - FieldEditor({ + final String fieldName; + + final IFieldContextLoader contextLoader; + const FieldEditor({ required this.gridId, - required this.fieldContextLoader, + required this.fieldName, + required this.contextLoader, Key? key, - }) : _fieldEditorBloc = getIt(param1: gridId, param2: fieldContextLoader) { - _fieldEditorBloc.add(const FieldEditorEvent.initial()); + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => FieldEditorBloc( + gridId: gridId, + fieldName: fieldName, + fieldContextLoader: contextLoader, + )..add(const FieldEditorEvent.initial()), + child: BlocBuilder( + buildWhen: (p, c) => false, + builder: (context, state) { + return ListView( + shrinkWrap: true, + children: [ + FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12), + const VSpace(10), + const _FieldNameTextField(), + const VSpace(10), + const _FieldPannel(), + ], + ); + }, + ), + ); } void show( @@ -30,7 +55,7 @@ class FieldEditor extends FlowyOverlayDelegate { FlowyOverlay.of(context).remove(identifier()); FlowyOverlay.of(context).insertWithAnchor( widget: OverlayContainer( - child: _FieldEditorWidget(_fieldEditorBloc, fieldContextLoader), + child: this, constraints: BoxConstraints.loose(const Size(280, 400)), ), identifier: identifier(), @@ -45,49 +70,23 @@ class FieldEditor extends FlowyOverlayDelegate { return (FieldEditor).toString(); } - @override - void didRemove() { - _fieldEditorBloc.add(const FieldEditorEvent.done()); - } - @override bool asBarrier() => true; } -class _FieldEditorWidget extends StatelessWidget { - final FieldEditorBloc editorBloc; - final EditFieldContextLoader fieldContextLoader; - const _FieldEditorWidget(this.editorBloc, this.fieldContextLoader, {Key? key}) : super(key: key); +class _FieldPannel extends StatelessWidget { + const _FieldPannel({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return BlocProvider.value( - value: editorBloc, - child: BlocBuilder( - builder: (context, state) { - return state.editFieldContext.fold( - () => const SizedBox(), - (editFieldContext) => ListView( - shrinkWrap: true, - children: [ - FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12), - const VSpace(10), - const _FieldNameTextField(), - const VSpace(10), - FieldEditorPannel( - editFieldContext: editFieldContext, - onSwitchToField: (fieldId, fieldType) { - return fieldContextLoader.switchToField(fieldId, fieldType); - }, - onUpdated: (field, typeOptionData) { - context.read().add(FieldEditorEvent.updateField(field, typeOptionData)); - }, - ), - ], - ), - ); - }, - ), + return BlocBuilder( + buildWhen: (p, c) => p.fieldContext != c.fieldContext, + builder: (context, state) { + return state.fieldContext.fold( + () => const SizedBox(), + (fieldContext) => FieldEditorPannel(fieldContext: fieldContext), + ); + }, ); } } @@ -97,16 +96,10 @@ class _FieldNameTextField extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocSelector( - selector: (state) { - return state.editFieldContext.fold( - () => "", - (editFieldContext) => editFieldContext.gridField.name, - ); - }, - builder: (context, name) { + return BlocBuilder( + builder: (context, state) { return FieldNameTextField( - name: name, + name: state.name, errorText: context.read().state.errorText, onNameChanged: (newName) { context.read().add(FieldEditorEvent.updateName(newName)); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart index b64cd4ad50..63b790b02c 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart @@ -1,20 +1,18 @@ import 'dart:typed_data'; +import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_type_option.dart'; import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart'; import 'package:dartz/dartz.dart' show Either; 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/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pbserver.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:app_flowy/startup/startup.dart'; 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/header/field_type_list.dart'; @@ -22,23 +20,21 @@ import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header import 'field_type_extension.dart'; import 'type_option/multi_select.dart'; import 'type_option/number.dart'; +import 'type_option/rich_text.dart'; import 'type_option/single_select.dart'; +import 'type_option/url.dart'; typedef UpdateFieldCallback = void Function(Field, Uint8List); -typedef SwitchToFieldCallback = Future> Function( +typedef SwitchToFieldCallback = Future> Function( String fieldId, FieldType fieldType, ); class FieldEditorPannel extends StatefulWidget { - final EditFieldContext editFieldContext; - final UpdateFieldCallback onUpdated; - final SwitchToFieldCallback onSwitchToField; + final GridFieldContext fieldContext; const FieldEditorPannel({ - required this.editFieldContext, - required this.onUpdated, - required this.onSwitchToField, + required this.fieldContext, Key? key, }) : super(key: key); @@ -52,13 +48,10 @@ class _FieldEditorPannelState extends State { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => getIt(param1: widget.editFieldContext), - child: BlocConsumer( - listener: (context, state) { - widget.onUpdated(state.field, state.typeOptionData); - }, + create: (context) => FieldEditorPannelBloc(widget.fieldContext)..add(const FieldEditorPannelEvent.initial()), + child: BlocBuilder( builder: (context, state) { - List children = [_switchFieldTypeButton(context, state.field)]; + List children = [_switchFieldTypeButton(context, widget.fieldContext.field)]; final typeOptionWidget = _typeOptionWidget(context: context, state: state); if (typeOptionWidget != null) { @@ -84,19 +77,7 @@ class _FieldEditorPannelState extends State { hoverColor: theme.hover, onTap: () { final list = FieldTypeList(onSelectField: (newFieldType) { - widget.onSwitchToField(field.id, newFieldType).then((result) { - result.fold( - (editFieldContext) { - context.read().add( - FieldEditorPannelEvent.toFieldType( - editFieldContext.gridField, - editFieldContext.typeOptionData, - ), - ); - }, - (err) => Log.error(err), - ); - }); + widget.fieldContext.switchToField(newFieldType); }); _showOverlay(context, list); }, @@ -115,18 +96,9 @@ class _FieldEditorPannelState extends State { hideOverlay: _hideOverlay, ); - final dataDelegate = TypeOptionDataDelegate(didUpdateTypeOptionData: (data) { - context.read().add(FieldEditorPannelEvent.didUpdateTypeOptionData(data)); - }); - final builder = _makeTypeOptionBuild( - typeOptionContext: TypeOptionContext( - gridId: state.gridId, - field: state.field, - data: state.typeOptionData, - ), + typeOptionContext: _makeTypeOptionContext(widget.fieldContext), overlayDelegate: overlayDelegate, - dataDelegate: dataDelegate, ); return builder.customWidget; @@ -166,25 +138,86 @@ abstract class TypeOptionBuilder { TypeOptionBuilder _makeTypeOptionBuild({ required TypeOptionContext typeOptionContext, required TypeOptionOverlayDelegate overlayDelegate, - required TypeOptionDataDelegate dataDelegate, }) { switch (typeOptionContext.field.fieldType) { case FieldType.Checkbox: - return CheckboxTypeOptionBuilder(typeOptionContext.data); + return CheckboxTypeOptionBuilder( + typeOptionContext as CheckboxTypeOptionContext, + ); case FieldType.DateTime: - return DateTypeOptionBuilder(typeOptionContext.data, overlayDelegate, dataDelegate); + return DateTypeOptionBuilder( + typeOptionContext as DateTypeOptionContext, + overlayDelegate, + ); case FieldType.SingleSelect: - return SingleSelectTypeOptionBuilder(typeOptionContext, overlayDelegate, dataDelegate); + return SingleSelectTypeOptionBuilder( + typeOptionContext as SingleSelectTypeOptionContext, + overlayDelegate, + ); case FieldType.MultiSelect: - return MultiSelectTypeOptionBuilder(typeOptionContext, overlayDelegate, dataDelegate); + return MultiSelectTypeOptionBuilder( + typeOptionContext as MultiSelectTypeOptionContext, + overlayDelegate, + ); case FieldType.Number: - return NumberTypeOptionBuilder(typeOptionContext.data, overlayDelegate, dataDelegate); + return NumberTypeOptionBuilder( + typeOptionContext as NumberTypeOptionContext, + overlayDelegate, + ); case FieldType.RichText: - return RichTextTypeOptionBuilder(typeOptionContext.data); + return RichTextTypeOptionBuilder( + typeOptionContext as RichTextTypeOptionContext, + ); - default: - throw UnimplementedError; + case FieldType.URL: + return URLTypeOptionBuilder( + typeOptionContext as URLTypeOptionContext, + ); } + throw UnimplementedError; +} + +TypeOptionContext _makeTypeOptionContext(GridFieldContext fieldContext) { + switch (fieldContext.field.fieldType) { + case FieldType.Checkbox: + return CheckboxTypeOptionContext( + fieldContext: fieldContext, + dataBuilder: CheckboxTypeOptionDataBuilder(), + ); + case FieldType.DateTime: + return DateTypeOptionContext( + fieldContext: fieldContext, + dataBuilder: DateTypeOptionDataBuilder(), + ); + case FieldType.MultiSelect: + return MultiSelectTypeOptionContext( + fieldContext: fieldContext, + dataBuilder: MultiSelectTypeOptionDataBuilder(), + ); + case FieldType.Number: + return NumberTypeOptionContext( + fieldContext: fieldContext, + dataBuilder: NumberTypeOptionDataBuilder(), + ); + case FieldType.RichText: + return RichTextTypeOptionContext( + fieldContext: fieldContext, + dataBuilder: RichTextTypeOptionDataBuilder(), + ); + case FieldType.SingleSelect: + return SingleSelectTypeOptionContext( + fieldContext: fieldContext, + dataBuilder: SingleSelectTypeOptionDataBuilder(), + ); + + case FieldType.URL: + return URLTypeOptionContext( + fieldContext: fieldContext, + dataBuilder: URLTypeOptionDataBuilder(), + ); + } + + throw UnimplementedError(); } abstract class TypeOptionWidget extends StatelessWidget { @@ -208,29 +241,3 @@ class TypeOptionOverlayDelegate { required this.hideOverlay, }); } - -class TypeOptionDataDelegate { - TypeOptionDataCallback didUpdateTypeOptionData; - - TypeOptionDataDelegate({ - required this.didUpdateTypeOptionData, - }); -} - -class RichTextTypeOptionBuilder extends TypeOptionBuilder { - RichTextTypeOption typeOption; - - RichTextTypeOptionBuilder(TypeOptionData typeOptionData) : typeOption = RichTextTypeOption.fromBuffer(typeOptionData); - - @override - Widget? get customWidget => null; -} - -class CheckboxTypeOptionBuilder extends TypeOptionBuilder { - CheckboxTypeOption typeOption; - - CheckboxTypeOptionBuilder(TypeOptionData typeOptionData) : typeOption = CheckboxTypeOption.fromBuffer(typeOptionData); - - @override - Widget? get customWidget => null; -} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_name_input.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_name_input.dart index 80cb327379..acc1256cbc 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_name_input.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_name_input.dart @@ -3,7 +3,7 @@ import 'package:flowy_infra_ui/widget/rounded_input_field.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class FieldNameTextField extends StatelessWidget { +class FieldNameTextField extends StatefulWidget { final void Function(String) onNameChanged; final String name; final String errorText; @@ -14,19 +14,41 @@ class FieldNameTextField extends StatelessWidget { Key? key, }) : super(key: key); + @override + State createState() => _FieldNameTextFieldState(); +} + +class _FieldNameTextFieldState extends State { + late String name; + TextEditingController controller = TextEditingController(); + + @override + void initState() { + controller.text = widget.name; + super.initState(); + } + @override Widget build(BuildContext context) { final theme = context.watch(); return RoundedInputField( height: 36, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500), - initialValue: name, + controller: controller, normalBorderColor: theme.shader4, errorBorderColor: theme.red, focusBorderColor: theme.main1, cursorColor: theme.main1, - errorText: errorText, - onChanged: onNameChanged, + errorText: widget.errorText, + onChanged: widget.onNameChanged, ); } + + @override + void didUpdateWidget(covariant FieldNameTextField oldWidget) { + controller.text = widget.name; + controller.selection = TextSelection.fromPosition(TextPosition(offset: controller.text.length)); + + super.didUpdateWidget(oldWidget); + } } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart index a4da8fa1b9..035d101544 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart @@ -17,9 +17,10 @@ extension FieldTypeListExtension on FieldType { return "grid/field/text"; case FieldType.SingleSelect: return "grid/field/single_select"; - default: - throw UnimplementedError; + case FieldType.URL: + return "grid/field/url"; } + throw UnimplementedError; } String title() { @@ -36,8 +37,9 @@ extension FieldTypeListExtension on FieldType { return LocaleKeys.grid_field_textFieldName.tr(); case FieldType.SingleSelect: return LocaleKeys.grid_field_singleSelectFieldName.tr(); - default: - throw UnimplementedError; + case FieldType.URL: + return LocaleKeys.grid_field_urlFieldName.tr(); } + throw UnimplementedError; } } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart index 63a397710c..5e1296191b 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart @@ -150,7 +150,8 @@ class CreateFieldButton extends StatelessWidget { hoverColor: theme.hover, onTap: () => FieldEditor( gridId: gridId, - fieldContextLoader: NewFieldContextLoader(gridId: gridId), + fieldName: "", + contextLoader: NewFieldContextLoader(gridId: gridId), ).show(context), leftIcon: svgWidget("home/add"), ); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart new file mode 100644 index 0000000000..c1e202f358 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart @@ -0,0 +1,20 @@ +import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart'; +import 'package:flutter/material.dart'; + +typedef CheckboxTypeOptionContext = TypeOptionContext; + +class CheckboxTypeOptionDataBuilder extends TypeOptionDataBuilder { + @override + CheckboxTypeOption fromBuffer(List buffer) { + return CheckboxTypeOption.fromBuffer(buffer); + } +} + +class CheckboxTypeOptionBuilder extends TypeOptionBuilder { + CheckboxTypeOptionBuilder(CheckboxTypeOptionContext typeOptionContext); + + @override + Widget? get customWidget => null; +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart index 677e043703..f4031dc9ce 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart @@ -1,4 +1,3 @@ -import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/grid/field/type_option/date_bloc.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; @@ -18,12 +17,10 @@ class DateTypeOptionBuilder extends TypeOptionBuilder { final DateTypeOptionWidget _widget; DateTypeOptionBuilder( - TypeOptionData typeOptionData, + DateTypeOptionContext typeOptionContext, TypeOptionOverlayDelegate overlayDelegate, - TypeOptionDataDelegate dataDelegate, ) : _widget = DateTypeOptionWidget( - typeOption: DateTypeOption.fromBuffer(typeOptionData), - dataDelegate: dataDelegate, + typeOptionContext: typeOptionContext, overlayDelegate: overlayDelegate, ); @@ -32,12 +29,11 @@ class DateTypeOptionBuilder extends TypeOptionBuilder { } class DateTypeOptionWidget extends TypeOptionWidget { - final DateTypeOption typeOption; + final DateTypeOptionContext typeOptionContext; final TypeOptionOverlayDelegate overlayDelegate; - final TypeOptionDataDelegate dataDelegate; + const DateTypeOptionWidget({ - required this.typeOption, - required this.dataDelegate, + required this.typeOptionContext, required this.overlayDelegate, Key? key, }) : super(key: key); @@ -45,9 +41,9 @@ class DateTypeOptionWidget extends TypeOptionWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => getIt(param1: typeOption), + create: (context) => DateTypeOptionBloc(typeOptionContext: typeOptionContext), child: BlocConsumer( - listener: (context, state) => dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()), + listener: (context, state) => typeOptionContext.typeOption = state.typeOption, builder: (context, state) { return Column(children: [ _renderDateFormatButton(context, state.typeOption.dateFormat), diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart index b5b3ece1a8..fc310410f9 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart @@ -1,22 +1,18 @@ -import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_bloc.dart'; -import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; +import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_type_option.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'field_option_pannel.dart'; +import 'select_option.dart'; class MultiSelectTypeOptionBuilder extends TypeOptionBuilder { final MultiSelectTypeOptionWidget _widget; MultiSelectTypeOptionBuilder( - TypeOptionContext typeOptionContext, + MultiSelectTypeOptionContext typeOptionContext, TypeOptionOverlayDelegate overlayDelegate, - TypeOptionDataDelegate dataDelegate, ) : _widget = MultiSelectTypeOptionWidget( typeOptionContext: typeOptionContext, overlayDelegate: overlayDelegate, - dataDelegate: dataDelegate, ); @override @@ -24,44 +20,23 @@ class MultiSelectTypeOptionBuilder extends TypeOptionBuilder { } class MultiSelectTypeOptionWidget extends TypeOptionWidget { - final TypeOptionContext typeOptionContext; + final MultiSelectTypeOptionContext typeOptionContext; final TypeOptionOverlayDelegate overlayDelegate; - final TypeOptionDataDelegate dataDelegate; + const MultiSelectTypeOptionWidget({ required this.typeOptionContext, required this.overlayDelegate, - required this.dataDelegate, Key? key, }) : super(key: key); @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => MultiSelectTypeOptionBloc(typeOptionContext), - child: BlocConsumer( - listener: (context, state) { - dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()); - }, - builder: (context, state) { - return FieldSelectOptionPannel( - options: state.typeOption.options, - beginEdit: () { - overlayDelegate.hideOverlay(context); - }, - createOptionCallback: (name) { - context.read().add(MultiSelectTypeOptionEvent.createOption(name)); - }, - updateOptionCallback: (updateOption) { - context.read().add(MultiSelectTypeOptionEvent.updateOption(updateOption)); - }, - deleteOptionCallback: (deleteOption) { - context.read().add(MultiSelectTypeOptionEvent.deleteOption(deleteOption)); - }, - overlayDelegate: overlayDelegate, - key: ValueKey(state.typeOption.hashCode), - ); - }, - ), + return SelectOptionTypeOptionWidget( + options: typeOptionContext.typeOption.options, + beginEdit: () => overlayDelegate.hideOverlay(context), + overlayDelegate: overlayDelegate, + typeOptionAction: typeOptionContext, + // key: ValueKey(state.typeOption.hashCode), ); } } 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 9e3ced1c43..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 @@ -1,4 +1,3 @@ -import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/grid/field/type_option/number_bloc.dart'; import 'package:app_flowy/workspace/application/grid/field/type_option/number_format_bloc.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; @@ -10,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; @@ -20,12 +19,10 @@ class NumberTypeOptionBuilder extends TypeOptionBuilder { final NumberTypeOptionWidget _widget; NumberTypeOptionBuilder( - TypeOptionData typeOptionData, + NumberTypeOptionContext typeOptionContext, TypeOptionOverlayDelegate overlayDelegate, - TypeOptionDataDelegate dataDelegate, ) : _widget = NumberTypeOptionWidget( - typeOption: NumberTypeOption.fromBuffer(typeOptionData), - dataDelegate: dataDelegate, + typeOptionContext: typeOptionContext, overlayDelegate: overlayDelegate, ); @@ -34,22 +31,23 @@ class NumberTypeOptionBuilder extends TypeOptionBuilder { } class NumberTypeOptionWidget extends TypeOptionWidget { - final TypeOptionDataDelegate dataDelegate; final TypeOptionOverlayDelegate overlayDelegate; - final NumberTypeOption typeOption; - const NumberTypeOptionWidget( - {required this.typeOption, required this.dataDelegate, required this.overlayDelegate, Key? key}) - : super(key: key); + final NumberTypeOptionContext typeOptionContext; + const NumberTypeOptionWidget({ + required this.typeOptionContext, + required this.overlayDelegate, + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { final theme = context.watch(); return BlocProvider( - create: (context) => getIt(param1: typeOption), + create: (context) => NumberTypeOptionBloc(typeOptionContext: typeOptionContext), child: SizedBox( height: GridSize.typeOptionItemHeight, child: BlocConsumer( - listener: (context, state) => dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()), + listener: (context, state) => typeOptionContext.typeOption = state.typeOption, builder: (context, state) { return FlowyButton( text: Row( diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/rich_text.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/rich_text.dart new file mode 100644 index 0000000000..03f9ed347c --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/rich_text.dart @@ -0,0 +1,21 @@ +import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart'; + +import 'package:flutter/material.dart'; + +typedef RichTextTypeOptionContext = TypeOptionContext; + +class RichTextTypeOptionDataBuilder extends TypeOptionDataBuilder { + @override + RichTextTypeOption fromBuffer(List buffer) { + return RichTextTypeOption.fromBuffer(buffer); + } +} + +class RichTextTypeOptionBuilder extends TypeOptionBuilder { + RichTextTypeOptionBuilder(RichTextTypeOptionContext typeOptionContext); + + @override + Widget? get customWidget => null; +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/field_option_pannel.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option.dart similarity index 57% rename from frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/field_option_pannel.dart rename to frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option.dart index ce6c69d2cd..b308995dd1 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/field_option_pannel.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option.dart @@ -1,5 +1,6 @@ -import 'package:app_flowy/workspace/application/grid/field/type_option/field_option_pannel_bloc.dart'; +import 'package:app_flowy/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; import 'package:flowy_infra/image.dart'; @@ -13,66 +14,39 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; -import 'edit_option_pannel.dart'; +import 'select_option_editor.dart'; -class FieldSelectOptionPannel extends StatelessWidget { +class SelectOptionTypeOptionWidget extends StatelessWidget { final List options; final VoidCallback beginEdit; - final Function(String optionName) createOptionCallback; - final Function(SelectOption) updateOptionCallback; - final Function(SelectOption) deleteOptionCallback; final TypeOptionOverlayDelegate overlayDelegate; + final SelectOptionTypeOptionAction typeOptionAction; - const FieldSelectOptionPannel({ + const SelectOptionTypeOptionWidget({ required this.options, required this.beginEdit, - required this.createOptionCallback, - required this.updateOptionCallback, - required this.deleteOptionCallback, required this.overlayDelegate, + required this.typeOptionAction, Key? key, }) : super(key: key); @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => FieldOptionPannelBloc(options: options), - child: BlocConsumer( - listener: (context, state) { - if (state.isEditingOption) { - beginEdit(); - } - state.newOptionName.fold( - () => null, - (optionName) => createOptionCallback(optionName), - ); - - state.updateOption.fold( - () => null, - (updateOption) => updateOptionCallback(updateOption), - ); - - state.deleteOption.fold( - () => null, - (deleteOption) => deleteOptionCallback(deleteOption), - ); - }, + create: (context) => SelectOptionTypeOptionBloc(options: options, typeOptionAction: typeOptionAction), + child: BlocBuilder( builder: (context, state) { List children = [ const TypeOptionSeparator(), const OptionTitle(), + if (state.isEditingOption) + const Padding( + padding: EdgeInsets.only(bottom: 10), + child: _CreateOptionTextField(), + ), + if (state.options.isEmpty && !state.isEditingOption) const _AddOptionButton(), + _OptionList(overlayDelegate) ]; - if (state.isEditingOption) { - children.add(const _OptionNameTextField()); - } - - if (state.options.isEmpty && !state.isEditingOption) { - children.add(const _AddOptionButton()); - } - - if (state.options.isNotEmpty) { - children.add(_OptionList(overlayDelegate)); - } return Column(children: children); }, @@ -86,30 +60,12 @@ class OptionTitle extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - - return BlocBuilder( + return BlocBuilder( builder: (context, state) { List children = [FlowyText.medium(LocaleKeys.grid_field_optionTitle.tr(), fontSize: 12)]; if (state.options.isNotEmpty) { children.add(const Spacer()); - children.add( - SizedBox( - width: 100, - height: 26, - child: FlowyButton( - text: FlowyText.medium( - LocaleKeys.grid_field_addOption.tr(), - fontSize: 12, - textAlign: TextAlign.center, - ), - hoverColor: theme.hover, - onTap: () { - context.read().add(const FieldOptionPannelEvent.beginAddingOption()); - }, - ), - ), - ); + children.add(const _OptionTitleButton()); } return SizedBox( @@ -121,13 +77,37 @@ class OptionTitle extends StatelessWidget { } } +class _OptionTitleButton extends StatelessWidget { + const _OptionTitleButton({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return SizedBox( + width: 100, + height: 26, + child: FlowyButton( + text: FlowyText.medium( + LocaleKeys.grid_field_addOption.tr(), + fontSize: 12, + textAlign: TextAlign.center, + ), + hoverColor: theme.hover, + onTap: () { + context.read().add(const SelectOptionTypeOptionEvent.addingOption()); + }, + ), + ); + } +} + class _OptionList extends StatelessWidget { final TypeOptionOverlayDelegate delegate; const _OptionList(this.delegate, {Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( buildWhen: (previous, current) { return previous.options != current.options; }, @@ -154,16 +134,16 @@ class _OptionList extends StatelessWidget { _OptionCell _makeOptionCell(BuildContext context, SelectOption option) { return _OptionCell( option: option, - onEdited: (option) { - final pannel = EditSelectOptionPannel( + onSelected: (option) { + final pannel = SelectOptionTypeOptionEditor( option: option, onDeleted: () { delegate.hideOverlay(context); - context.read().add(FieldOptionPannelEvent.deleteOption(option)); + context.read().add(SelectOptionTypeOptionEvent.deleteOption(option)); }, onUpdated: (updatedOption) { delegate.hideOverlay(context); - context.read().add(FieldOptionPannelEvent.updateOption(updatedOption)); + context.read().add(SelectOptionTypeOptionEvent.updateOption(updatedOption)); }, key: ValueKey(option.id), ); @@ -175,23 +155,28 @@ class _OptionList extends StatelessWidget { class _OptionCell extends StatelessWidget { final SelectOption option; - final Function(SelectOption) onEdited; + final Function(SelectOption) onSelected; const _OptionCell({ required this.option, - required this.onEdited, + required this.onSelected, Key? key, }) : super(key: key); @override Widget build(BuildContext context) { final theme = context.watch(); + return SizedBox( height: GridSize.typeOptionItemHeight, - child: FlowyButton( - text: FlowyText.medium(option.name, fontSize: 12), - hoverColor: theme.hover, - onTap: () => onEdited(option), - rightIcon: svgWidget("grid/details", color: theme.iconColor), + child: SelectOptionTagCell( + option: option, + onSelected: onSelected, + children: [ + svgWidget( + "grid/details", + color: theme.iconColor, + ), + ], ), ); } @@ -209,7 +194,7 @@ class _AddOptionButton extends StatelessWidget { text: FlowyText.medium(LocaleKeys.grid_field_addSelectOption.tr(), fontSize: 12), hoverColor: theme.hover, onTap: () { - context.read().add(const FieldOptionPannelEvent.beginAddingOption()); + context.read().add(const SelectOptionTypeOptionEvent.addingOption()); }, leftIcon: svgWidget("home/add", color: theme.iconColor), ), @@ -217,18 +202,24 @@ class _AddOptionButton extends StatelessWidget { } } -class _OptionNameTextField extends StatelessWidget { - const _OptionNameTextField({Key? key}) : super(key: key); +class _CreateOptionTextField extends StatelessWidget { + const _CreateOptionTextField({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return InputTextField( - text: "", - onCanceled: () { - context.read().add(const FieldOptionPannelEvent.endAddingOption()); - }, - onDone: (optionName) { - context.read().add(FieldOptionPannelEvent.createOption(optionName)); + return BlocBuilder( + builder: (context, state) { + final text = state.newOptionName.foldRight("", (a, previous) => a); + return InputTextField( + autoClearWhenDone: true, + text: text, + onCanceled: () { + context.read().add(const SelectOptionTypeOptionEvent.endAddingOption()); + }, + onDone: (optionName) { + context.read().add(SelectOptionTypeOptionEvent.createOption(optionName)); + }, + ); }, ); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart similarity index 96% rename from frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart rename to frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart index 62c4eca7e5..cec32bd99e 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart @@ -1,6 +1,6 @@ import 'package:app_flowy/workspace/application/grid/field/type_option/edit_select_option_bloc.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; @@ -14,17 +14,19 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; -class EditSelectOptionPannel extends StatelessWidget { +class SelectOptionTypeOptionEditor extends StatelessWidget { final SelectOption option; final VoidCallback onDeleted; final Function(SelectOption) onUpdated; - const EditSelectOptionPannel({ + const SelectOptionTypeOptionEditor({ required this.option, required this.onDeleted, required this.onUpdated, 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/header/type_option/single_select.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart index f8cc2293f6..fedddec11d 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart @@ -1,20 +1,16 @@ -import 'package:app_flowy/workspace/application/grid/field/type_option/single_select_bloc.dart'; -import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; +import 'package:app_flowy/workspace/application/grid/field/type_option/single_select_type_option.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'field_option_pannel.dart'; +import 'select_option.dart'; class SingleSelectTypeOptionBuilder extends TypeOptionBuilder { final SingleSelectTypeOptionWidget _widget; SingleSelectTypeOptionBuilder( - TypeOptionContext typeOptionContext, + SingleSelectTypeOptionContext typeOptionContext, TypeOptionOverlayDelegate overlayDelegate, - TypeOptionDataDelegate dataDelegate, ) : _widget = SingleSelectTypeOptionWidget( typeOptionContext: typeOptionContext, - dataDelegate: dataDelegate, overlayDelegate: overlayDelegate, ); @@ -23,44 +19,23 @@ class SingleSelectTypeOptionBuilder extends TypeOptionBuilder { } class SingleSelectTypeOptionWidget extends TypeOptionWidget { - final TypeOptionContext typeOptionContext; + final SingleSelectTypeOptionContext typeOptionContext; final TypeOptionOverlayDelegate overlayDelegate; - final TypeOptionDataDelegate dataDelegate; + const SingleSelectTypeOptionWidget({ required this.typeOptionContext, - required this.dataDelegate, required this.overlayDelegate, Key? key, }) : super(key: key); @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => SingleSelectTypeOptionBloc(typeOptionContext), - child: BlocConsumer( - listener: (context, state) { - dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()); - }, - builder: (context, state) { - return FieldSelectOptionPannel( - options: state.typeOption.options, - beginEdit: () { - overlayDelegate.hideOverlay(context); - }, - createOptionCallback: (name) { - context.read().add(SingleSelectTypeOptionEvent.createOption(name)); - }, - updateOptionCallback: (updateOption) { - context.read().add(SingleSelectTypeOptionEvent.updateOption(updateOption)); - }, - deleteOptionCallback: (deleteOption) { - context.read().add(SingleSelectTypeOptionEvent.deleteOption(deleteOption)); - }, - overlayDelegate: overlayDelegate, - key: ValueKey(state.typeOption.hashCode), - ); - }, - ), + return SelectOptionTypeOptionWidget( + options: typeOptionContext.typeOption.options, + beginEdit: () => overlayDelegate.hideOverlay(context), + overlayDelegate: overlayDelegate, + typeOptionAction: typeOptionContext, + // key: ValueKey(state.typeOption.hashCode), ); } } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart new file mode 100644 index 0000000000..f4e73f7fdc --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart @@ -0,0 +1,20 @@ +import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; +import 'package:flutter/material.dart'; + +typedef URLTypeOptionContext = TypeOptionContext; + +class URLTypeOptionDataBuilder extends TypeOptionDataBuilder { + @override + URLTypeOption fromBuffer(List buffer) { + return URLTypeOption.fromBuffer(buffer); + } +} + +class URLTypeOptionBuilder extends TypeOptionBuilder { + URLTypeOptionBuilder(URLTypeOptionContext typeOptionContext); + + @override + Widget? get customWidget => null; +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart deleted file mode 100644 index 0f3f7c5f32..0000000000 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:app_flowy/startup/startup.dart'; -import 'package:app_flowy/workspace/application/grid/prelude.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class NumberCell extends StatefulWidget { - final GridCell cellData; - - const NumberCell({ - required this.cellData, - Key? key, - }) : super(key: key); - - @override - State createState() => _NumberCellState(); -} - -class _NumberCellState extends State { - late NumberCellBloc _cellBloc; - - @override - void initState() { - _cellBloc = getIt(param1: widget.cellData); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return BlocProvider.value( - value: _cellBloc, - child: BlocBuilder( - builder: (context, state) { - return Container(); - }, - ), - ); - } - - @override - Future dispose() async { - _cellBloc.close(); - super.dispose(); - } -} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart index 200a079d55..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,25 +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( - onPressed: onExpand, - iconPadding: const EdgeInsets.fromLTRB(6, 6, 6, 6), - fillColor: theme.surface, - 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 a6f86cafee..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,13 +3,14 @@ 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'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart'; 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'; @@ -67,14 +68,15 @@ class _RowDetailPageState extends State { return bloc; }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 40), + padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20), child: Column( children: [ SizedBox( - height: 40, - child: Row( - children: const [Spacer(), _CloseButton()], - )), + height: 40, + child: Row( + children: const [Spacer(), _CloseButton()], + ), + ), Expanded(child: _PropertyList(cellCache: widget.cellCache)), ], ), @@ -147,30 +149,33 @@ 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 SizedBox( - height: 36, - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 150, - child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)), - ), - const HSpace(10), - Expanded( - child: FlowyHover2( - child: cell, - contentPadding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), + + return ConstrainedBox( + constraints: const BoxConstraints(minHeight: 40), + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 150, + child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)), ), - ), - ], + const HSpace(10), + Expanded(child: gesture), + ], + ), ), ); } @@ -178,7 +183,8 @@ class _RowDetailCell extends StatelessWidget { void _showFieldEditor(BuildContext context) { FieldEditor( gridId: gridCell.gridId, - fieldContextLoader: FieldContextLoaderAdaptor( + fieldName: gridCell.field.name, + contextLoader: FieldContextLoader( gridId: gridCell.gridId, field: gridCell.field, ), @@ -186,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; @@ -208,7 +214,15 @@ GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) { return SelectOptionCellStyle( placeholder: LocaleKeys.grid_row_textPlaceholder.tr(), ); - default: - return null; + + case FieldType.URL: + return GridURLCellStyle( + placeholder: LocaleKeys.grid_row_textPlaceholder.tr(), + accessoryTypes: [ + GridURLCellAccessoryType.edit, + GridURLCellAccessoryType.copyURL, + ], + ); } + 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_property.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart index aa8f88ab5d..c971b48479 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart @@ -115,7 +115,8 @@ class _GridPropertyCell extends StatelessWidget { onTap: () { FieldEditor( gridId: gridId, - fieldContextLoader: FieldContextLoaderAdaptor(gridId: gridId, field: field), + fieldName: field.name, + contextLoader: FieldContextLoader(gridId: gridId, field: field), ).show(context, anchorDirection: AnchorDirection.bottomRight); }, ); 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/emoji_picker/src/emoji_picker.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker.dart index f1e4420252..650cd185f1 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker.dart @@ -135,7 +135,7 @@ class _EmojiPickerState extends State { if (!loaded) { // Load emojis updateEmojiFuture.then( - (value) => WidgetsBinding.instance!.addPostFrameCallback((_) { + (value) => WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; setState(() { loaded = true; diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart index 91ce8d07ec..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; } }); @@ -62,63 +61,23 @@ class QuestionBubble extends StatelessWidget { } _launchURL(String url) async { - if (await canLaunch(url)) { - await launch(url); + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); } else { throw 'Could not launch $url'; } } } -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/linux/flutter/generated_plugins.cmake b/frontend/app_flowy/linux/flutter/generated_plugins.cmake index 5562f19113..c7ae414da2 100644 --- a/frontend/app_flowy/linux/flutter/generated_plugins.cmake +++ b/frontend/app_flowy/linux/flutter/generated_plugins.cmake @@ -8,6 +8,9 @@ list(APPEND FLUTTER_PLUGIN_LIST window_size ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -16,3 +19,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/frontend/app_flowy/macos/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 08e2493683..61cb05b101 100644 --- a/frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj +++ b/frontend/app_flowy/macos/Runner.xcodeproj/project.pbxproj @@ -421,6 +421,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + EXCLUDED_ARCHS = ""; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -552,6 +553,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + EXCLUDED_ARCHS = ""; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -575,6 +577,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + EXCLUDED_ARCHS = ""; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/frontend/app_flowy/macos/Runner/MainFlutterWindow.swift b/frontend/app_flowy/macos/Runner/MainFlutterWindow.swift index 2722837ec9..8e357d7ca1 100644 --- a/frontend/app_flowy/macos/Runner/MainFlutterWindow.swift +++ b/frontend/app_flowy/macos/Runner/MainFlutterWindow.swift @@ -1,12 +1,82 @@ import Cocoa import FlutterMacOS +private let kTrafficLightOffetTop = 22 + class MainFlutterWindow: NSWindow { + func registerMethodChannel(flutterViewController: FlutterViewController) { + let cocoaWindowChannel = FlutterMethodChannel(name: "flutter/cocoaWindow", binaryMessenger: flutterViewController.engine.binaryMessenger) + cocoaWindowChannel.setMethodCallHandler({ + (call: FlutterMethodCall, result: FlutterResult) -> Void in + if call.method == "setWindowPosition" { + guard let position = call.arguments as? NSArray else { + result(nil) + return + } + let nX = position[0] as! NSNumber + let nY = position[1] as! NSNumber + let x = nX.doubleValue + let y = nY.doubleValue + + self.setFrameOrigin(NSPoint(x: x, y: y)) + result(nil) + return + } else if call.method == "getWindowPosition" { + let frame = self.frame + result([frame.origin.x, frame.origin.y]) + return + } else if call.method == "zoom" { + self.zoom(self) + result(nil) + return + } + + result(FlutterMethodNotImplemented) + }) + } + + func layoutTrafficLightButton(titlebarView: NSView, button: NSButton, offsetTop: CGFloat, offsetLeft: CGFloat) { + button.translatesAutoresizingMaskIntoConstraints = false; + titlebarView.addConstraint(NSLayoutConstraint.init( + item: button, + attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: titlebarView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: offsetTop)) + titlebarView.addConstraint(NSLayoutConstraint.init( + item: button, + attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: titlebarView, attribute: NSLayoutConstraint.Attribute.left, multiplier: 1, constant: offsetLeft)) + } + + func layoutTrafficLights() { + let closeButton = self.standardWindowButton(ButtonType.closeButton)! + let minButton = self.standardWindowButton(ButtonType.miniaturizeButton)! + let zoomButton = self.standardWindowButton(ButtonType.zoomButton)! + let titlebarView = closeButton.superview! + + self.layoutTrafficLightButton(titlebarView: titlebarView, button: closeButton, offsetTop: CGFloat(kTrafficLightOffetTop), offsetLeft: 20) + self.layoutTrafficLightButton(titlebarView: titlebarView, button: minButton, offsetTop: CGFloat(kTrafficLightOffetTop), offsetLeft: 38) + self.layoutTrafficLightButton(titlebarView: titlebarView, button: zoomButton, offsetTop: CGFloat(kTrafficLightOffetTop), offsetLeft: 56) + + let customToolbar = NSTitlebarAccessoryViewController() + let newView = NSView() + newView.frame = NSRect(origin: CGPoint(), size: CGSize(width: 0, height: 40)) // only the height is cared + customToolbar.view = newView + self.addTitlebarAccessoryViewController(customToolbar) + } + override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController + + self.registerMethodChannel(flutterViewController: flutterViewController) + self.setFrame(windowFrame, display: true) + self.titlebarAppearsTransparent = true + self.titleVisibility = .hidden + self.styleMask.insert(StyleMask.fullSizeContentView) + self.isMovableByWindowBackground = true + self.isMovable = false + + self.layoutTrafficLights() RegisterGeneratedPlugins(registry: flutterViewController) diff --git a/frontend/app_flowy/packages/flowy_infra/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 5189908192..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,112 +99,3 @@ class FlowyHoverContainer extends StatelessWidget { ); } } - -// -abstract class HoverWidget extends StatefulWidget { - const HoverWidget({Key? key}) : super(key: key); - - ValueNotifier get onFocus; -} - -class FlowyHover2 extends StatefulWidget { - final HoverWidget 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; - - @override - void initState() { - _hoverState = FlowyHoverState(); - widget.child.onFocus.addListener(() { - _hoverState.onFocus = widget.child.onFocus.value; - }); - super.initState(); - } - - @override - void dispose() { - _hoverState.dispose(); - 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_infra_ui/lib/widget/rounded_input_field.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart index 712fba3c6c..37eb782a2c 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart @@ -132,9 +132,12 @@ class _RoundedInputFieldState extends State { children.add( Align( alignment: Alignment.centerLeft, - child: Text( - widget.errorText, - style: widget.style, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Text( + widget.errorText, + style: widget.style, + ), ), ), ); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart index a1208075f5..0149f63b3a 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart @@ -124,14 +124,14 @@ class GridEventSwitchToField { EditFieldPayload request; GridEventSwitchToField(this.request); - Future> send() { + Future> send() { final request = FFIRequest.create() ..event = GridEvent.SwitchToField.toString() ..payload = requestToBytes(this.request); return Dispatch.asyncRequest(request) .then((bytesResult) => bytesResult.fold( - (okBytes) => left(EditFieldContext.fromBuffer(okBytes)), + (okBytes) => left(FieldTypeOptionData.fromBuffer(okBytes)), (errBytes) => right(FlowyError.fromBuffer(errBytes)), )); } @@ -154,23 +154,6 @@ class GridEventDuplicateField { } } -class GridEventGetEditFieldContext { - EditFieldPayload request; - GridEventGetEditFieldContext(this.request); - - Future> send() { - final request = FFIRequest.create() - ..event = GridEvent.GetEditFieldContext.toString() - ..payload = requestToBytes(this.request); - - return Dispatch.asyncRequest(request) - .then((bytesResult) => bytesResult.fold( - (okBytes) => left(EditFieldContext.fromBuffer(okBytes)), - (errBytes) => right(FlowyError.fromBuffer(errBytes)), - )); - } -} - class GridEventMoveItem { MoveItemPayload request; GridEventMoveItem(this.request); @@ -205,6 +188,23 @@ class GridEventGetFieldTypeOption { } } +class GridEventCreateFieldTypeOption { + EditFieldPayload request; + GridEventCreateFieldTypeOption(this.request); + + Future> send() { + final request = FFIRequest.create() + ..event = GridEvent.CreateFieldTypeOption.toString() + ..payload = requestToBytes(this.request); + + return Dispatch.asyncRequest(request) + .then((bytesResult) => bytesResult.fold( + (okBytes) => left(FieldTypeOptionData.fromBuffer(okBytes)), + (errBytes) => right(FlowyError.fromBuffer(errBytes)), + )); + } +} + class GridEventNewSelectOption { CreateSelectOptionPayload request; GridEventNewSelectOption(this.request); @@ -392,20 +392,3 @@ class GridEventUpdateDateCell { } } -class GridEventGetDateCellData { - CellIdentifierPayload request; - GridEventGetDateCellData(this.request); - - Future> send() { - final request = FFIRequest.create() - ..event = GridEvent.GetDateCellData.toString() - ..payload = requestToBytes(this.request); - - return Dispatch.asyncRequest(request) - .then((bytesResult) => bytesResult.fold( - (okBytes) => left(DateCellData.fromBuffer(okBytes)), - (errBytes) => right(FlowyError.fromBuffer(errBytes)), - )); - } -} - diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/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-data-model/grid.pb.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart index 4c18294dae..87f9036c88 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart @@ -490,21 +490,12 @@ class GetEditFieldContextPayload extends $pb.GeneratedMessage { void clearFieldType() => clearField(3); } -enum EditFieldPayload_OneOfFieldId { - fieldId, - notSet -} - class EditFieldPayload extends $pb.GeneratedMessage { - static const $core.Map<$core.int, EditFieldPayload_OneOfFieldId> _EditFieldPayload_OneOfFieldIdByTag = { - 2 : EditFieldPayload_OneOfFieldId.fieldId, - 0 : EditFieldPayload_OneOfFieldId.notSet - }; static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'EditFieldPayload', createEmptyInstance: create) - ..oo(0, [2]) ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId') ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId') ..e(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldType', $pb.PbFieldType.OE, defaultOrMaker: FieldType.RichText, valueOf: FieldType.valueOf, enumValues: FieldType.values) + ..aOB(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'createIfNotExist') ..hasRequiredFields = false ; @@ -513,6 +504,7 @@ class EditFieldPayload extends $pb.GeneratedMessage { $core.String? gridId, $core.String? fieldId, FieldType? fieldType, + $core.bool? createIfNotExist, }) { final _result = create(); if (gridId != null) { @@ -524,6 +516,9 @@ class EditFieldPayload extends $pb.GeneratedMessage { if (fieldType != null) { _result.fieldType = fieldType; } + if (createIfNotExist != null) { + _result.createIfNotExist = createIfNotExist; + } return _result; } factory EditFieldPayload.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); @@ -547,9 +542,6 @@ class EditFieldPayload extends $pb.GeneratedMessage { static EditFieldPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static EditFieldPayload? _defaultInstance; - EditFieldPayload_OneOfFieldId whichOneOfFieldId() => _EditFieldPayload_OneOfFieldIdByTag[$_whichOneof(0)]!; - void clearOneOfFieldId() => clearField($_whichOneof(0)); - @$pb.TagNumber(1) $core.String get gridId => $_getSZ(0); @$pb.TagNumber(1) @@ -576,18 +568,27 @@ class EditFieldPayload extends $pb.GeneratedMessage { $core.bool hasFieldType() => $_has(2); @$pb.TagNumber(3) void clearFieldType() => clearField(3); + + @$pb.TagNumber(4) + $core.bool get createIfNotExist => $_getBF(3); + @$pb.TagNumber(4) + set createIfNotExist($core.bool v) { $_setBool(3, v); } + @$pb.TagNumber(4) + $core.bool hasCreateIfNotExist() => $_has(3); + @$pb.TagNumber(4) + void clearCreateIfNotExist() => clearField(4); } -class EditFieldContext extends $pb.GeneratedMessage { - static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'EditFieldContext', createEmptyInstance: create) +class FieldTypeOptionContext extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'FieldTypeOptionContext', createEmptyInstance: create) ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId') ..aOM(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridField', subBuilder: Field.create) ..a<$core.List<$core.int>>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptionData', $pb.PbFieldType.OY) ..hasRequiredFields = false ; - EditFieldContext._() : super(); - factory EditFieldContext({ + FieldTypeOptionContext._() : super(); + factory FieldTypeOptionContext({ $core.String? gridId, Field? gridField, $core.List<$core.int>? typeOptionData, @@ -604,26 +605,26 @@ class EditFieldContext extends $pb.GeneratedMessage { } return _result; } - factory EditFieldContext.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); - factory EditFieldContext.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + factory FieldTypeOptionContext.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory FieldTypeOptionContext.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @$core.Deprecated( 'Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 'Will be removed in next major version') - EditFieldContext clone() => EditFieldContext()..mergeFromMessage(this); + FieldTypeOptionContext clone() => FieldTypeOptionContext()..mergeFromMessage(this); @$core.Deprecated( 'Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 'Will be removed in next major version') - EditFieldContext copyWith(void Function(EditFieldContext) updates) => super.copyWith((message) => updates(message as EditFieldContext)) as EditFieldContext; // ignore: deprecated_member_use + FieldTypeOptionContext copyWith(void Function(FieldTypeOptionContext) updates) => super.copyWith((message) => updates(message as FieldTypeOptionContext)) as FieldTypeOptionContext; // ignore: deprecated_member_use $pb.BuilderInfo get info_ => _i; @$core.pragma('dart2js:noInline') - static EditFieldContext create() => EditFieldContext._(); - EditFieldContext createEmptyInstance() => create(); - static $pb.PbList createRepeated() => $pb.PbList(); + static FieldTypeOptionContext create() => FieldTypeOptionContext._(); + FieldTypeOptionContext createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); @$core.pragma('dart2js:noInline') - static EditFieldContext getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); - static EditFieldContext? _defaultInstance; + static FieldTypeOptionContext getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static FieldTypeOptionContext? _defaultInstance; @$pb.TagNumber(1) $core.String get gridId => $_getSZ(0); @@ -657,19 +658,24 @@ class EditFieldContext extends $pb.GeneratedMessage { class FieldTypeOptionData extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'FieldTypeOptionData', createEmptyInstance: create) - ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId') - ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptionData', $pb.PbFieldType.OY) + ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId') + ..aOM(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'field', subBuilder: Field.create) + ..a<$core.List<$core.int>>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptionData', $pb.PbFieldType.OY) ..hasRequiredFields = false ; FieldTypeOptionData._() : super(); factory FieldTypeOptionData({ - $core.String? fieldId, + $core.String? gridId, + Field? field_2, $core.List<$core.int>? typeOptionData, }) { final _result = create(); - if (fieldId != null) { - _result.fieldId = fieldId; + if (gridId != null) { + _result.gridId = gridId; + } + if (field_2 != null) { + _result.field_2 = field_2; } if (typeOptionData != null) { _result.typeOptionData = typeOptionData; @@ -698,22 +704,33 @@ class FieldTypeOptionData extends $pb.GeneratedMessage { static FieldTypeOptionData? _defaultInstance; @$pb.TagNumber(1) - $core.String get fieldId => $_getSZ(0); + $core.String get gridId => $_getSZ(0); @$pb.TagNumber(1) - set fieldId($core.String v) { $_setString(0, v); } + set gridId($core.String v) { $_setString(0, v); } @$pb.TagNumber(1) - $core.bool hasFieldId() => $_has(0); + $core.bool hasGridId() => $_has(0); @$pb.TagNumber(1) - void clearFieldId() => clearField(1); + void clearGridId() => clearField(1); @$pb.TagNumber(2) - $core.List<$core.int> get typeOptionData => $_getN(1); + Field get field_2 => $_getN(1); @$pb.TagNumber(2) - set typeOptionData($core.List<$core.int> v) { $_setBytes(1, v); } + set field_2(Field v) { setField(2, v); } @$pb.TagNumber(2) - $core.bool hasTypeOptionData() => $_has(1); + $core.bool hasField_2() => $_has(1); @$pb.TagNumber(2) - void clearTypeOptionData() => clearField(2); + void clearField_2() => clearField(2); + @$pb.TagNumber(2) + Field ensureField_2() => $_ensure(1); + + @$pb.TagNumber(3) + $core.List<$core.int> get typeOptionData => $_getN(2); + @$pb.TagNumber(3) + set typeOptionData($core.List<$core.int> v) { $_setBytes(2, v); } + @$pb.TagNumber(3) + $core.bool hasTypeOptionData() => $_has(2); + @$pb.TagNumber(3) + void clearTypeOptionData() => clearField(3); } class RepeatedField extends $pb.GeneratedMessage { @@ -1349,24 +1366,19 @@ class GridBlock extends $pb.GeneratedMessage { class Cell extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Cell', createEmptyInstance: create) ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId') - ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'content') - ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data') + ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data', $pb.PbFieldType.OY) ..hasRequiredFields = false ; Cell._() : super(); factory Cell({ $core.String? fieldId, - $core.String? content, - $core.String? data, + $core.List<$core.int>? data, }) { final _result = create(); if (fieldId != null) { _result.fieldId = fieldId; } - if (content != null) { - _result.content = content; - } if (data != null) { _result.data = data; } @@ -1403,22 +1415,13 @@ class Cell extends $pb.GeneratedMessage { void clearFieldId() => clearField(1); @$pb.TagNumber(2) - $core.String get content => $_getSZ(1); + $core.List<$core.int> get data => $_getN(1); @$pb.TagNumber(2) - set content($core.String v) { $_setString(1, v); } + set data($core.List<$core.int> v) { $_setBytes(1, v); } @$pb.TagNumber(2) - $core.bool hasContent() => $_has(1); + $core.bool hasData() => $_has(1); @$pb.TagNumber(2) - void clearContent() => clearField(2); - - @$pb.TagNumber(3) - $core.String get data => $_getSZ(2); - @$pb.TagNumber(3) - set data($core.String v) { $_setString(2, v); } - @$pb.TagNumber(3) - $core.bool hasData() => $_has(2); - @$pb.TagNumber(3) - void clearData() => clearField(3); + void clearData() => clearField(2); } class RepeatedCell extends $pb.GeneratedMessage { diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbenum.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbenum.dart index e6cb17314b..78331a46e5 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbenum.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbenum.dart @@ -31,6 +31,7 @@ class FieldType extends $pb.ProtobufEnum { static const FieldType SingleSelect = FieldType._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SingleSelect'); static const FieldType MultiSelect = FieldType._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MultiSelect'); static const FieldType Checkbox = FieldType._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Checkbox'); + static const FieldType URL = FieldType._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'URL'); static const $core.List values = [ RichText, @@ -39,6 +40,7 @@ class FieldType extends $pb.ProtobufEnum { SingleSelect, MultiSelect, Checkbox, + URL, ]; static final $core.Map<$core.int, FieldType> _byValue = $pb.ProtobufEnum.initByValue(values); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart index 3155b79a2e..7c28fa1ceb 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart @@ -29,11 +29,12 @@ const FieldType$json = const { const {'1': 'SingleSelect', '2': 3}, const {'1': 'MultiSelect', '2': 4}, const {'1': 'Checkbox', '2': 5}, + const {'1': 'URL', '2': 6}, ], }; /// Descriptor for `FieldType`. Decode as a `google.protobuf.EnumDescriptorProto`. -final $typed_data.Uint8List fieldTypeDescriptor = $convert.base64Decode('CglGaWVsZFR5cGUSDAoIUmljaFRleHQQABIKCgZOdW1iZXIQARIMCghEYXRlVGltZRACEhAKDFNpbmdsZVNlbGVjdBADEg8KC011bHRpU2VsZWN0EAQSDAoIQ2hlY2tib3gQBQ=='); +final $typed_data.Uint8List fieldTypeDescriptor = $convert.base64Decode('CglGaWVsZFR5cGUSDAoIUmljaFRleHQQABIKCgZOdW1iZXIQARIMCghEYXRlVGltZRACEhAKDFNpbmdsZVNlbGVjdBADEg8KC011bHRpU2VsZWN0EAQSDAoIQ2hlY2tib3gQBRIHCgNVUkwQBg=='); @$core.Deprecated('Use gridDescriptor instead') const Grid$json = const { '1': 'Grid', @@ -117,19 +118,17 @@ const EditFieldPayload$json = const { '1': 'EditFieldPayload', '2': const [ const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'}, - const {'1': 'field_id', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'fieldId'}, + const {'1': 'field_id', '3': 2, '4': 1, '5': 9, '10': 'fieldId'}, const {'1': 'field_type', '3': 3, '4': 1, '5': 14, '6': '.FieldType', '10': 'fieldType'}, - ], - '8': const [ - const {'1': 'one_of_field_id'}, + const {'1': 'create_if_not_exist', '3': 4, '4': 1, '5': 8, '10': 'createIfNotExist'}, ], }; /// Descriptor for `EditFieldPayload`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List editFieldPayloadDescriptor = $convert.base64Decode('ChBFZGl0RmllbGRQYXlsb2FkEhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIbCghmaWVsZF9pZBgCIAEoCUgAUgdmaWVsZElkEikKCmZpZWxkX3R5cGUYAyABKA4yCi5GaWVsZFR5cGVSCWZpZWxkVHlwZUIRCg9vbmVfb2ZfZmllbGRfaWQ='); -@$core.Deprecated('Use editFieldContextDescriptor instead') -const EditFieldContext$json = const { - '1': 'EditFieldContext', +final $typed_data.Uint8List editFieldPayloadDescriptor = $convert.base64Decode('ChBFZGl0RmllbGRQYXlsb2FkEhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIZCghmaWVsZF9pZBgCIAEoCVIHZmllbGRJZBIpCgpmaWVsZF90eXBlGAMgASgOMgouRmllbGRUeXBlUglmaWVsZFR5cGUSLQoTY3JlYXRlX2lmX25vdF9leGlzdBgEIAEoCFIQY3JlYXRlSWZOb3RFeGlzdA=='); +@$core.Deprecated('Use fieldTypeOptionContextDescriptor instead') +const FieldTypeOptionContext$json = const { + '1': 'FieldTypeOptionContext', '2': const [ const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'}, const {'1': 'grid_field', '3': 2, '4': 1, '5': 11, '6': '.Field', '10': 'gridField'}, @@ -137,19 +136,20 @@ const EditFieldContext$json = const { ], }; -/// Descriptor for `EditFieldContext`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List editFieldContextDescriptor = $convert.base64Decode('ChBFZGl0RmllbGRDb250ZXh0EhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIlCgpncmlkX2ZpZWxkGAIgASgLMgYuRmllbGRSCWdyaWRGaWVsZBIoChB0eXBlX29wdGlvbl9kYXRhGAMgASgMUg50eXBlT3B0aW9uRGF0YQ=='); +/// Descriptor for `FieldTypeOptionContext`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List fieldTypeOptionContextDescriptor = $convert.base64Decode('ChZGaWVsZFR5cGVPcHRpb25Db250ZXh0EhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIlCgpncmlkX2ZpZWxkGAIgASgLMgYuRmllbGRSCWdyaWRGaWVsZBIoChB0eXBlX29wdGlvbl9kYXRhGAMgASgMUg50eXBlT3B0aW9uRGF0YQ=='); @$core.Deprecated('Use fieldTypeOptionDataDescriptor instead') const FieldTypeOptionData$json = const { '1': 'FieldTypeOptionData', '2': const [ - const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'}, - const {'1': 'type_option_data', '3': 2, '4': 1, '5': 12, '10': 'typeOptionData'}, + const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'}, + const {'1': 'field', '3': 2, '4': 1, '5': 11, '6': '.Field', '10': 'field'}, + const {'1': 'type_option_data', '3': 3, '4': 1, '5': 12, '10': 'typeOptionData'}, ], }; /// Descriptor for `FieldTypeOptionData`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List fieldTypeOptionDataDescriptor = $convert.base64Decode('ChNGaWVsZFR5cGVPcHRpb25EYXRhEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEigKEHR5cGVfb3B0aW9uX2RhdGEYAiABKAxSDnR5cGVPcHRpb25EYXRh'); +final $typed_data.Uint8List fieldTypeOptionDataDescriptor = $convert.base64Decode('ChNGaWVsZFR5cGVPcHRpb25EYXRhEhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIcCgVmaWVsZBgCIAEoCzIGLkZpZWxkUgVmaWVsZBIoChB0eXBlX29wdGlvbl9kYXRhGAMgASgMUg50eXBlT3B0aW9uRGF0YQ=='); @$core.Deprecated('Use repeatedFieldDescriptor instead') const RepeatedField$json = const { '1': 'RepeatedField', @@ -290,13 +290,12 @@ const Cell$json = const { '1': 'Cell', '2': const [ const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'}, - const {'1': 'content', '3': 2, '4': 1, '5': 9, '10': 'content'}, - const {'1': 'data', '3': 3, '4': 1, '5': 9, '10': 'data'}, + const {'1': 'data', '3': 2, '4': 1, '5': 12, '10': 'data'}, ], }; /// Descriptor for `Cell`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List cellDescriptor = $convert.base64Decode('CgRDZWxsEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEhgKB2NvbnRlbnQYAiABKAlSB2NvbnRlbnQSEgoEZGF0YRgDIAEoCVIEZGF0YQ=='); +final $typed_data.Uint8List cellDescriptor = $convert.base64Decode('CgRDZWxsEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEhIKBGRhdGEYAiABKAxSBGRhdGE='); @$core.Deprecated('Use repeatedCellDescriptor instead') const RepeatedCell$json = const { '1': 'RepeatedCell', diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart index 0035c0f24d..863162cc3e 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart @@ -19,9 +19,9 @@ class GridEvent extends $pb.ProtobufEnum { static const GridEvent DeleteField = GridEvent._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteField'); static const GridEvent SwitchToField = GridEvent._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SwitchToField'); static const GridEvent DuplicateField = GridEvent._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateField'); - static const GridEvent GetEditFieldContext = GridEvent._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetEditFieldContext'); - static const GridEvent MoveItem = GridEvent._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MoveItem'); - static const GridEvent GetFieldTypeOption = GridEvent._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetFieldTypeOption'); + static const GridEvent MoveItem = GridEvent._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MoveItem'); + static const GridEvent GetFieldTypeOption = GridEvent._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetFieldTypeOption'); + static const GridEvent CreateFieldTypeOption = GridEvent._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateFieldTypeOption'); static const GridEvent NewSelectOption = GridEvent._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NewSelectOption'); static const GridEvent GetSelectOptionCellData = GridEvent._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetSelectOptionCellData'); static const GridEvent UpdateSelectOption = GridEvent._(32, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateSelectOption'); @@ -33,7 +33,6 @@ class GridEvent extends $pb.ProtobufEnum { static const GridEvent UpdateCell = GridEvent._(71, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateCell'); static const GridEvent UpdateSelectOptionCell = GridEvent._(72, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateSelectOptionCell'); static const GridEvent UpdateDateCell = GridEvent._(80, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateDateCell'); - static const GridEvent GetDateCellData = GridEvent._(90, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetDateCellData'); static const $core.List values = [ GetGridData, @@ -45,9 +44,9 @@ class GridEvent extends $pb.ProtobufEnum { DeleteField, SwitchToField, DuplicateField, - GetEditFieldContext, MoveItem, GetFieldTypeOption, + CreateFieldTypeOption, NewSelectOption, GetSelectOptionCellData, UpdateSelectOption, @@ -59,7 +58,6 @@ class GridEvent extends $pb.ProtobufEnum { UpdateCell, UpdateSelectOptionCell, UpdateDateCell, - GetDateCellData, ]; static final $core.Map<$core.int, GridEvent> _byValue = $pb.ProtobufEnum.initByValue(values); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart index 5088625002..08de369a01 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart @@ -21,9 +21,9 @@ const GridEvent$json = const { const {'1': 'DeleteField', '2': 14}, const {'1': 'SwitchToField', '2': 20}, const {'1': 'DuplicateField', '2': 21}, - const {'1': 'GetEditFieldContext', '2': 22}, - const {'1': 'MoveItem', '2': 23}, - const {'1': 'GetFieldTypeOption', '2': 24}, + const {'1': 'MoveItem', '2': 22}, + const {'1': 'GetFieldTypeOption', '2': 23}, + const {'1': 'CreateFieldTypeOption', '2': 24}, const {'1': 'NewSelectOption', '2': 30}, const {'1': 'GetSelectOptionCellData', '2': 31}, const {'1': 'UpdateSelectOption', '2': 32}, @@ -35,9 +35,8 @@ const GridEvent$json = const { const {'1': 'UpdateCell', '2': 71}, const {'1': 'UpdateSelectOptionCell', '2': 72}, const {'1': 'UpdateDateCell', '2': 80}, - const {'1': 'GetDateCellData', '2': 90}, ], }; /// Descriptor for `GridEvent`. Decode as a `google.protobuf.EnumDescriptorProto`. -final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIZChVVcGRhdGVGaWVsZFR5cGVPcHRpb24QDBIPCgtJbnNlcnRGaWVsZBANEg8KC0RlbGV0ZUZpZWxkEA4SEQoNU3dpdGNoVG9GaWVsZBAUEhIKDkR1cGxpY2F0ZUZpZWxkEBUSFwoTR2V0RWRpdEZpZWxkQ29udGV4dBAWEgwKCE1vdmVJdGVtEBcSFgoSR2V0RmllbGRUeXBlT3B0aW9uEBgSEwoPTmV3U2VsZWN0T3B0aW9uEB4SGwoXR2V0U2VsZWN0T3B0aW9uQ2VsbERhdGEQHxIWChJVcGRhdGVTZWxlY3RPcHRpb24QIBINCglDcmVhdGVSb3cQMhIKCgZHZXRSb3cQMxINCglEZWxldGVSb3cQNBIQCgxEdXBsaWNhdGVSb3cQNRILCgdHZXRDZWxsEEYSDgoKVXBkYXRlQ2VsbBBHEhoKFlVwZGF0ZVNlbGVjdE9wdGlvbkNlbGwQSBISCg5VcGRhdGVEYXRlQ2VsbBBQEhMKD0dldERhdGVDZWxsRGF0YRBa'); +final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIZChVVcGRhdGVGaWVsZFR5cGVPcHRpb24QDBIPCgtJbnNlcnRGaWVsZBANEg8KC0RlbGV0ZUZpZWxkEA4SEQoNU3dpdGNoVG9GaWVsZBAUEhIKDkR1cGxpY2F0ZUZpZWxkEBUSDAoITW92ZUl0ZW0QFhIWChJHZXRGaWVsZFR5cGVPcHRpb24QFxIZChVDcmVhdGVGaWVsZFR5cGVPcHRpb24QGBITCg9OZXdTZWxlY3RPcHRpb24QHhIbChdHZXRTZWxlY3RPcHRpb25DZWxsRGF0YRAfEhYKElVwZGF0ZVNlbGVjdE9wdGlvbhAgEg0KCUNyZWF0ZVJvdxAyEgoKBkdldFJvdxAzEg0KCURlbGV0ZVJvdxA0EhAKDER1cGxpY2F0ZVJvdxA1EgsKB0dldENlbGwQRhIOCgpVcGRhdGVDZWxsEEcSGgoWVXBkYXRlU2VsZWN0T3B0aW9uQ2VsbBBIEhIKDlVwZGF0ZURhdGVDZWxsEFA='); 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 af6583c106..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 @@ -5,7 +5,9 @@ export './dart_notification.pb.dart'; export './selection_type_option.pb.dart'; export './row_entities.pb.dart'; export './cell_entities.pb.dart'; +export './url_type_option.pb.dart'; export './checkbox_type_option.pb.dart'; +export './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-grid/text_type_option.pb.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pb.dart index c30f2eb6e1..a38a68be36 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pb.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pb.dart @@ -11,17 +11,17 @@ import 'package:protobuf/protobuf.dart' as $pb; class RichTextTypeOption extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RichTextTypeOption', createEmptyInstance: create) - ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'format') + ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data') ..hasRequiredFields = false ; RichTextTypeOption._() : super(); factory RichTextTypeOption({ - $core.String? format, + $core.String? data, }) { final _result = create(); - if (format != null) { - _result.format = format; + if (data != null) { + _result.data = data; } return _result; } @@ -47,12 +47,12 @@ class RichTextTypeOption extends $pb.GeneratedMessage { static RichTextTypeOption? _defaultInstance; @$pb.TagNumber(1) - $core.String get format => $_getSZ(0); + $core.String get data => $_getSZ(0); @$pb.TagNumber(1) - set format($core.String v) { $_setString(0, v); } + set data($core.String v) { $_setString(0, v); } @$pb.TagNumber(1) - $core.bool hasFormat() => $_has(0); + $core.bool hasData() => $_has(0); @$pb.TagNumber(1) - void clearFormat() => clearField(1); + void clearData() => clearField(1); } diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pbjson.dart index e4ba6956ee..5999ce87e0 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pbjson.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_type_option.pbjson.dart @@ -12,9 +12,9 @@ import 'dart:typed_data' as $typed_data; const RichTextTypeOption$json = const { '1': 'RichTextTypeOption', '2': const [ - const {'1': 'format', '3': 1, '4': 1, '5': 9, '10': 'format'}, + const {'1': 'data', '3': 1, '4': 1, '5': 9, '10': 'data'}, ], }; /// Descriptor for `RichTextTypeOption`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List richTextTypeOptionDescriptor = $convert.base64Decode('ChJSaWNoVGV4dFR5cGVPcHRpb24SFgoGZm9ybWF0GAEgASgJUgZmb3JtYXQ='); +final $typed_data.Uint8List richTextTypeOptionDescriptor = $convert.base64Decode('ChJSaWNoVGV4dFR5cGVPcHRpb24SEgoEZGF0YRgBIAEoCVIEZGF0YQ=='); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pb.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pb.dart new file mode 100644 index 0000000000..c43474a92a --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pb.dart @@ -0,0 +1,119 @@ +/// +// Generated code. Do not modify. +// source: url_type_option.proto +// +// @dart = 2.12 +// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +class URLTypeOption extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'URLTypeOption', createEmptyInstance: create) + ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data') + ..hasRequiredFields = false + ; + + URLTypeOption._() : super(); + factory URLTypeOption({ + $core.String? data, + }) { + final _result = create(); + if (data != null) { + _result.data = data; + } + return _result; + } + factory URLTypeOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory URLTypeOption.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + URLTypeOption clone() => URLTypeOption()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + URLTypeOption copyWith(void Function(URLTypeOption) updates) => super.copyWith((message) => updates(message as URLTypeOption)) as URLTypeOption; // ignore: deprecated_member_use + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static URLTypeOption create() => URLTypeOption._(); + URLTypeOption createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static URLTypeOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static URLTypeOption? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get data => $_getSZ(0); + @$pb.TagNumber(1) + set data($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasData() => $_has(0); + @$pb.TagNumber(1) + void clearData() => clearField(1); +} + +class URLCellData extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'URLCellData', createEmptyInstance: create) + ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'url') + ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'content') + ..hasRequiredFields = false + ; + + URLCellData._() : super(); + factory URLCellData({ + $core.String? url, + $core.String? content, + }) { + final _result = create(); + if (url != null) { + _result.url = url; + } + if (content != null) { + _result.content = content; + } + return _result; + } + factory URLCellData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory URLCellData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + URLCellData clone() => URLCellData()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + URLCellData copyWith(void Function(URLCellData) updates) => super.copyWith((message) => updates(message as URLCellData)) as URLCellData; // ignore: deprecated_member_use + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static URLCellData create() => URLCellData._(); + URLCellData createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static URLCellData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static URLCellData? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get url => $_getSZ(0); + @$pb.TagNumber(1) + set url($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasUrl() => $_has(0); + @$pb.TagNumber(1) + void clearUrl() => clearField(1); + + @$pb.TagNumber(2) + $core.String get content => $_getSZ(1); + @$pb.TagNumber(2) + set content($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasContent() => $_has(1); + @$pb.TagNumber(2) + void clearContent() => clearField(2); +} + diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbenum.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbenum.dart new file mode 100644 index 0000000000..de8793d432 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbenum.dart @@ -0,0 +1,7 @@ +/// +// Generated code. Do not modify. +// source: url_type_option.proto +// +// @dart = 2.12 +// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields + diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbjson.dart new file mode 100644 index 0000000000..30ac81dfb2 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbjson.dart @@ -0,0 +1,31 @@ +/// +// Generated code. Do not modify. +// source: url_type_option.proto +// +// @dart = 2.12 +// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package + +import 'dart:core' as $core; +import 'dart:convert' as $convert; +import 'dart:typed_data' as $typed_data; +@$core.Deprecated('Use uRLTypeOptionDescriptor instead') +const URLTypeOption$json = const { + '1': 'URLTypeOption', + '2': const [ + const {'1': 'data', '3': 1, '4': 1, '5': 9, '10': 'data'}, + ], +}; + +/// Descriptor for `URLTypeOption`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List uRLTypeOptionDescriptor = $convert.base64Decode('Cg1VUkxUeXBlT3B0aW9uEhIKBGRhdGEYASABKAlSBGRhdGE='); +@$core.Deprecated('Use uRLCellDataDescriptor instead') +const URLCellData$json = const { + '1': 'URLCellData', + '2': const [ + const {'1': 'url', '3': 1, '4': 1, '5': 9, '10': 'url'}, + const {'1': 'content', '3': 2, '4': 1, '5': 9, '10': 'content'}, + ], +}; + +/// Descriptor for `URLCellData`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List uRLCellDataDescriptor = $convert.base64Decode('CgtVUkxDZWxsRGF0YRIQCgN1cmwYASABKAlSA3VybBIYCgdjb250ZW50GAIgASgJUgdjb250ZW50'); diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbserver.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbserver.dart new file mode 100644 index 0000000000..6889e31393 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/url_type_option.pbserver.dart @@ -0,0 +1,9 @@ +/// +// Generated code. Do not modify. +// source: url_type_option.proto +// +// @dart = 2.12 +// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package + +export 'url_type_option.pb.dart'; + diff --git a/frontend/app_flowy/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.lock b/frontend/app_flowy/pubspec.lock index 8f7e6d6db2..958debd9dd 100644 --- a/frontend/app_flowy/pubspec.lock +++ b/frontend/app_flowy/pubspec.lock @@ -7,28 +7,28 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "31.0.0" + version: "38.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "2.8.0" + version: "3.4.1" animations: dependency: transitive description: name: animations url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.3" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "2.3.0" + version: "2.3.1" async: dependency: transitive description: @@ -42,14 +42,14 @@ packages: name: bloc url: "https://pub.dartlang.org" source: hosted - version: "8.0.2" + version: "8.0.3" bloc_test: dependency: "direct dev" description: name: bloc_test url: "https://pub.dartlang.org" source: hosted - version: "9.0.2" + version: "9.0.3" boolean_selector: dependency: transitive description: @@ -63,7 +63,7 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.3.0" build_config: dependency: transitive description: @@ -77,21 +77,21 @@ packages: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.1.0" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.8" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "2.1.7" + version: "2.1.11" build_runner_core: dependency: transitive description: @@ -112,7 +112,7 @@ packages: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "8.1.4" + version: "8.3.2" characters: dependency: transitive description: @@ -134,13 +134,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" - cli_util: - dependency: transitive - description: - name: cli_util - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.5" clipboard: dependency: "direct main" description: @@ -168,7 +161,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" connectivity_plus: dependency: "direct main" description: @@ -182,14 +175,14 @@ packages: name: connectivity_plus_linux url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" connectivity_plus_macos: dependency: transitive description: name: connectivity_plus_macos url: "https://pub.dartlang.org" source: hosted - version: "1.2.1" + version: "1.2.2" connectivity_plus_platform_interface: dependency: transitive description: @@ -224,21 +217,21 @@ packages: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.3.2" cross_file: dependency: transitive description: name: cross_file url: "https://pub.dartlang.org" source: hosted - version: "0.3.2" + version: "0.3.3+1" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" csslib: dependency: transitive description: @@ -259,7 +252,7 @@ packages: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.2.3" dartz: dependency: transitive description: @@ -273,14 +266,14 @@ packages: name: dbus url: "https://pub.dartlang.org" source: hosted - version: "0.6.8" + version: "0.7.3" device_info_plus: dependency: "direct main" description: name: device_info_plus url: "https://pub.dartlang.org" source: hosted - version: "3.2.1" + version: "3.2.3" device_info_plus_linux: dependency: transitive description: @@ -294,7 +287,7 @@ packages: name: device_info_plus_macos url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.2.3" device_info_plus_platform_interface: dependency: transitive description: @@ -329,7 +322,7 @@ packages: name: easy_localization url: "https://pub.dartlang.org" source: hosted - version: "3.0.1-dev" + version: "3.0.1" easy_logger: dependency: transitive description: @@ -357,14 +350,14 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" ffi: dependency: transitive description: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.2.1" file: dependency: transitive description: @@ -378,7 +371,7 @@ packages: name: fixnum url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" flowy_infra: dependency: "direct main" description: @@ -439,14 +432,14 @@ packages: name: flutter_inappwebview url: "https://pub.dartlang.org" source: hosted - version: "5.3.2" + version: "5.4.3+7" flutter_keyboard_visibility: dependency: transitive description: name: flutter_keyboard_visibility url: "https://pub.dartlang.org" source: hosted - version: "5.1.1" + version: "5.2.0" flutter_keyboard_visibility_platform_interface: dependency: transitive description: @@ -479,13 +472,13 @@ packages: name: flutter_plugin_android_lifecycle url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.6" flutter_quill: dependency: "direct main" description: path: "." - ref: dbc309f5e382963fa0122409a0aaf8e1417d51c1 - resolved-ref: dbc309f5e382963fa0122409a0aaf8e1417d51c1 + ref: "306fd78b7a134abdde0fed6be67f59e8a6068509" + resolved-ref: "306fd78b7a134abdde0fed6be67f59e8a6068509" url: "https://github.com/appflowy/flutter-quill.git" source: git version: "2.0.13" @@ -519,21 +512,21 @@ packages: name: freezed url: "https://pub.dartlang.org" source: hosted - version: "0.14.5" + version: "2.0.3+1" freezed_annotation: dependency: "direct main" description: name: freezed_annotation url: "https://pub.dartlang.org" source: hosted - version: "0.14.3" + version: "2.0.3" frontend_server_client: dependency: transitive description: name: frontend_server_client url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "2.1.3" get_it: dependency: "direct main" description: @@ -582,42 +575,56 @@ packages: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.2.0" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.0.1" i18n_extension: dependency: transitive description: name: i18n_extension url: "https://pub.dartlang.org" source: hosted - version: "4.2.0" + version: "4.2.1" image_picker: dependency: transitive description: name: image_picker url: "https://pub.dartlang.org" source: hosted - version: "0.8.4+4" + version: "0.8.5+3" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.4+13" image_picker_for_web: dependency: transitive description: name: image_picker_for_web url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "2.1.8" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.5+5" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.4.3" + version: "2.5.0" intl: dependency: "direct main" description: @@ -645,14 +652,14 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3" + version: "0.6.4" json_annotation: dependency: transitive description: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "4.4.0" + version: "4.5.0" linked_scroll_controller: dependency: "direct main" description: @@ -666,7 +673,7 @@ packages: name: lint url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" lints: dependency: transitive description: @@ -680,7 +687,7 @@ packages: name: loading_indicator url: "https://pub.dartlang.org" source: hosted - version: "3.0.2" + version: "3.1.0" logger: dependency: transitive description: @@ -708,7 +715,7 @@ packages: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "0.1.4" meta: dependency: transitive description: @@ -722,14 +729,14 @@ packages: name: mime url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" mocktail: dependency: transitive description: name: mocktail url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "0.3.0" nested: dependency: transitive description: @@ -743,7 +750,7 @@ packages: name: nm url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.5.0" node_preamble: dependency: transitive description: @@ -764,14 +771,14 @@ packages: name: package_info_plus url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.4.2" package_info_plus_linux: dependency: transitive description: name: package_info_plus_linux url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.5" package_info_plus_macos: dependency: transitive description: @@ -792,21 +799,21 @@ packages: name: package_info_plus_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.5" package_info_plus_windows: dependency: transitive description: name: package_info_plus_windows url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.5" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" path_drawing: dependency: transitive description: @@ -827,49 +834,49 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.0.10" path_provider_android: dependency: transitive description: name: path_provider_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.11" + version: "2.0.14" path_provider_ios: dependency: transitive description: name: path_provider_ios url: "https://pub.dartlang.org" source: hosted - version: "2.0.7" + version: "2.0.9" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "2.1.6" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.6" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.4" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.6" pedantic: dependency: transitive description: @@ -883,7 +890,7 @@ packages: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "4.4.0" + version: "5.0.0" photo_view: dependency: transitive description: @@ -932,14 +939,14 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "6.0.2" + version: "6.0.3" pub_semver: dependency: transitive description: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" pubspec_parse: dependency: transitive description: @@ -953,49 +960,49 @@ packages: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "3.0.1+1" + version: "3.1.0" reorderables: dependency: "direct main" description: name: reorderables url: "https://pub.dartlang.org" source: hosted - version: "0.4.3" + version: "0.5.0" shared_preferences: dependency: transitive description: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "2.0.12" + version: "2.0.15" shared_preferences_android: dependency: transitive description: name: shared_preferences_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.10" + version: "2.0.12" shared_preferences_ios: dependency: transitive description: name: shared_preferences_ios url: "https://pub.dartlang.org" source: hosted - version: "2.0.9" + version: "2.1.1" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "2.1.1" shared_preferences_macos: dependency: transitive description: name: shared_preferences_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.4" shared_preferences_platform_interface: dependency: transitive description: @@ -1009,21 +1016,21 @@ packages: name: shared_preferences_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.4" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "2.1.1" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" shelf_packages_handler: dependency: transitive description: @@ -1070,7 +1077,7 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "1.2.1" + version: "1.2.2" source_map_stack_trace: dependency: transitive description: @@ -1091,7 +1098,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" sprintf: dependency: transitive description: @@ -1161,28 +1168,28 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.19.5" + version: "1.21.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.8" + version: "0.4.9" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.13" textfield_tags: dependency: "direct main" description: name: textfield_tags url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.0+1" textstyle_extensions: dependency: transitive description: @@ -1217,7 +1224,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" universal_platform: dependency: transitive description: @@ -1231,35 +1238,35 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "6.0.18" + version: "6.1.2" url_launcher_android: dependency: transitive description: name: url_launcher_android url: "https://pub.dartlang.org" source: hosted - version: "6.0.14" + version: "6.0.17" url_launcher_ios: dependency: transitive description: name: url_launcher_ios url: "https://pub.dartlang.org" source: hosted - version: "6.0.14" + version: "6.0.17" url_launcher_linux: dependency: transitive description: name: url_launcher_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "3.0.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "3.0.1" url_launcher_platform_interface: dependency: transitive description: @@ -1273,56 +1280,70 @@ packages: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.11" url_launcher_windows: dependency: transitive description: name: url_launcher_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "3.0.1" uuid: dependency: transitive description: name: uuid url: "https://pub.dartlang.org" source: hosted - version: "3.0.5" + version: "3.0.6" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" video_player: dependency: transitive description: name: video_player url: "https://pub.dartlang.org" source: hosted - version: "2.2.11" + version: "2.4.2" + video_player_android: + dependency: transitive + description: + name: video_player_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.4" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.4" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "5.0.1" + version: "5.1.2" video_player_web: dependency: transitive description: name: video_player_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.10" vm_service: dependency: transitive description: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "7.5.0" + version: "8.3.0" watcher: dependency: transitive description: @@ -1336,21 +1357,21 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.2.0" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.1.0" win32: dependency: transitive description: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.3.6" + version: "2.6.1" window_size: dependency: "direct main" description: @@ -1366,28 +1387,28 @@ packages: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "0.2.0+1" xml: dependency: transitive description: name: xml url: "https://pub.dartlang.org" source: hosted - version: "5.3.1" + version: "5.4.1" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "3.1.1" youtube_player_flutter: dependency: transitive description: name: youtube_player_flutter url: "https://pub.dartlang.org" source: hosted - version: "8.0.0" + version: "8.1.0" sdks: - dart: ">=2.15.0-116.0.dev <3.0.0" - flutter: ">=2.5.0" + dart: ">=2.17.0 <3.0.0" + flutter: ">=3.0.0" diff --git a/frontend/app_flowy/pubspec.yaml b/frontend/app_flowy/pubspec.yaml index fd2b75b9e0..8dbc3b4c70 100644 --- a/frontend/app_flowy/pubspec.yaml +++ b/frontend/app_flowy/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.15.0-116.0.dev <3.0.0" + sdk: ">=2.16.2 <3.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -40,7 +40,7 @@ dependencies: flutter_quill: git: url: https://github.com/appflowy/flutter-quill.git - ref: dbc309f5e382963fa0122409a0aaf8e1417d51c1 + ref: 306fd78b7a134abdde0fed6be67f59e8a6068509 # third party packages intl: ^0.17.0 @@ -72,9 +72,9 @@ 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: + reorderables: ^0.5.0 linked_scroll_controller: ^0.2.0 dev_dependencies: diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 554def4acd..57cfede8cb 100755 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -928,6 +928,7 @@ dependencies = [ "dart-notify", "dashmap", "diesel", + "fancy-regex", "flowy-database", "flowy-derive", "flowy-error", @@ -936,6 +937,7 @@ dependencies = [ "flowy-revision", "flowy-sync", "flowy-test", + "futures", "indexmap", "lazy_static", "lib-dispatch", @@ -953,6 +955,7 @@ dependencies = [ "strum_macros", "tokio", "tracing", + "url", ] [[package]] diff --git a/frontend/rust-lib/flowy-folder/src/manager.rs b/frontend/rust-lib/flowy-folder/src/manager.rs index 05847f8cc1..3181c14625 100644 --- a/frontend/rust-lib/flowy-folder/src/manager.rs +++ b/frontend/rust-lib/flowy-folder/src/manager.rs @@ -4,7 +4,7 @@ use crate::{ errors::FlowyResult, event_map::{FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser}, services::{ - folder_editor::ClientFolderEditor, persistence::FolderPersistence, set_current_workspace, AppController, + folder_editor::FolderEditor, persistence::FolderPersistence, set_current_workspace, AppController, TrashController, ViewController, WorkspaceController, }, }; @@ -61,7 +61,7 @@ pub struct FolderManager { pub(crate) view_controller: Arc, pub(crate) trash_controller: Arc, web_socket: Arc, - folder_editor: Arc>>>, + folder_editor: Arc>>>, data_processors: ViewDataProcessorMap, } @@ -166,8 +166,7 @@ impl FolderManager { let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), disk_cache)); let rev_manager = RevisionManager::new(user_id, folder_id.as_ref(), rev_persistence); - let folder_editor = - ClientFolderEditor::new(user_id, &folder_id, token, rev_manager, self.web_socket.clone()).await?; + let folder_editor = FolderEditor::new(user_id, &folder_id, token, rev_manager, self.web_socket.clone()).await?; *self.folder_editor.write().await = Some(Arc::new(folder_editor)); let _ = self.app_controller.initialize()?; @@ -228,7 +227,7 @@ impl DefaultFolderBuilder { #[cfg(feature = "flowy_unit_test")] impl FolderManager { - pub async fn folder_editor(&self) -> Arc { + pub async fn folder_editor(&self) -> Arc { self.folder_editor.read().await.clone().unwrap() } } @@ -242,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/app/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs index 45034646b0..b95b91dc2e 100644 --- a/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs +++ b/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs @@ -46,7 +46,7 @@ pub(crate) async fn update_app_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, app_controller, view_controller))] +#[tracing::instrument(level = "trace", skip(data, app_controller, view_controller))] pub(crate) async fn read_app_handler( data: Data, app_controller: AppData>, diff --git a/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs b/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs index 6ea1a7e2ec..ef94c1d316 100644 --- a/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs +++ b/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs @@ -17,7 +17,7 @@ use lib_ot::core::PlainTextAttributes; use parking_lot::RwLock; use std::sync::Arc; -pub struct ClientFolderEditor { +pub struct FolderEditor { user_id: String, #[allow(dead_code)] pub(crate) folder_id: FolderId, @@ -27,7 +27,7 @@ pub struct ClientFolderEditor { ws_manager: Arc, } -impl ClientFolderEditor { +impl FolderEditor { #[allow(unused_variables)] pub async fn new( user_id: &str, @@ -129,7 +129,7 @@ impl RevisionCloudService for FolderRevisionCloudService { } #[cfg(feature = "flowy_unit_test")] -impl ClientFolderEditor { +impl FolderEditor { pub fn rev_manager(&self) -> Arc { self.rev_manager.clone() } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs index b272168e33..d5bbb3a277 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs @@ -5,7 +5,7 @@ mod version_2; use crate::{ event_map::WorkspaceDatabase, manager::FolderId, - services::{folder_editor::ClientFolderEditor, persistence::migration::FolderMigration}, + services::{folder_editor::FolderEditor, persistence::migration::FolderMigration}, }; use flowy_database::ConnectionPool; use flowy_error::{FlowyError, FlowyResult}; @@ -50,14 +50,11 @@ pub trait FolderPersistenceTransaction { pub struct FolderPersistence { database: Arc, - folder_editor: Arc>>>, + folder_editor: Arc>>>, } impl FolderPersistence { - pub fn new( - database: Arc, - folder_editor: Arc>>>, - ) -> Self { + pub fn new(database: Arc, folder_editor: Arc>>>) -> Self { Self { database, folder_editor, diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs index 8dafda3c93..40f72e10b0 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs @@ -1,5 +1,5 @@ use crate::services::{ - folder_editor::ClientFolderEditor, + folder_editor::FolderEditor, persistence::{AppChangeset, FolderPersistenceTransaction, ViewChangeset, WorkspaceChangeset}, }; use flowy_error::{FlowyError, FlowyResult}; @@ -11,7 +11,7 @@ use flowy_folder_data_model::entities::{ }; use std::sync::Arc; -impl FolderPersistenceTransaction for ClientFolderEditor { +impl FolderPersistenceTransaction for FolderEditor { fn create_workspace(&self, _user_id: &str, workspace: Workspace) -> FlowyResult<()> { if let Some(change) = self.folder.write().create_workspace(workspace)? { let _ = self.apply_change(change)?; diff --git a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs b/frontend/rust-lib/flowy-folder/src/services/view/controller.rs index 603b76ec2c..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) @@ -129,7 +129,7 @@ impl ViewController { .await } - #[tracing::instrument(level = "debug", skip(self), err)] + #[tracing::instrument(level = "trace", skip(self), err)] pub(crate) fn set_latest_view(&self, view_id: &str) -> Result<(), FlowyError> { KV::set_str(LATEST_VIEW_ID, view_id.to_owned()); Ok(()) @@ -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), @@ -193,7 +193,7 @@ impl ViewController { } // belong_to_id will be the app_id or view_id. - #[tracing::instrument(level = "debug", skip(self), err)] + #[tracing::instrument(level = "trace", skip(self), err)] pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result { self.persistence .begin_transaction(|transaction| { @@ -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-folder/tests/workspace/script.rs b/frontend/rust-lib/flowy-folder/tests/workspace/script.rs index a6fd1fbe29..39f023ca7d 100644 --- a/frontend/rust-lib/flowy-folder/tests/workspace/script.rs +++ b/frontend/rust-lib/flowy-folder/tests/workspace/script.rs @@ -1,5 +1,5 @@ use flowy_folder::event_map::FolderEvent::*; -use flowy_folder::{errors::ErrorCode, services::folder_editor::ClientFolderEditor}; +use flowy_folder::{errors::ErrorCode, services::folder_editor::FolderEditor}; use flowy_folder_data_model::entities::view::{RepeatedViewId, ViewId}; use flowy_folder_data_model::entities::workspace::WorkspaceId; use flowy_folder_data_model::entities::{ @@ -125,7 +125,7 @@ impl FolderTest { pub async fn run_script(&mut self, script: FolderScript) { let sdk = &self.sdk; - let folder_editor: Arc = sdk.folder_manager.folder_editor().await; + let folder_editor: Arc = sdk.folder_manager.folder_editor().await; let rev_manager = folder_editor.rev_manager(); let cache = rev_manager.revision_cache().await; diff --git a/frontend/rust-lib/flowy-grid/Cargo.toml b/frontend/rust-lib/flowy-grid/Cargo.toml index 1691b3eee7..1cf2ee0e53 100644 --- a/frontend/rust-lib/flowy-grid/Cargo.toml +++ b/frontend/rust-lib/flowy-grid/Cargo.toml @@ -35,6 +35,9 @@ serde = { version = "1.0", features = ["derive"] } serde_json = {version = "1.0"} serde_repr = "0.1" indexmap = {version = "1.8.1", features = ["serde"]} +fancy-regex = "0.10.0" +url = { version = "2"} +futures = "0.3.15" [dev-dependencies] flowy-test = { path = "../flowy-test" } diff --git a/frontend/rust-lib/flowy-grid/Flowy.toml b/frontend/rust-lib/flowy-grid/Flowy.toml index 1d0d2baea8..835988d41e 100644 --- a/frontend/rust-lib/flowy-grid/Flowy.toml +++ b/frontend/rust-lib/flowy-grid/Flowy.toml @@ -2,7 +2,7 @@ proto_crates = [ "src/event_map.rs", "src/services/field/type_options", - "src/services/entities", + "src/entities", "src/dart_notification.rs" ] event_files = ["src/event_map.rs"] \ No newline at end of file diff --git a/frontend/rust-lib/flowy-grid/src/services/entities/cell_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs similarity index 96% rename from frontend/rust-lib/flowy-grid/src/services/entities/cell_entities.rs rename to frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs index 2f408db992..4d09fe0eda 100644 --- a/frontend/rust-lib/flowy-grid/src/services/entities/cell_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs @@ -1,4 +1,4 @@ -use crate::services::entities::{FieldIdentifier, FieldIdentifierPayload}; +use crate::entities::{FieldIdentifier, FieldIdentifierPayload}; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; use flowy_grid_data_model::parser::NotEmptyStr; diff --git a/frontend/rust-lib/flowy-grid/src/services/entities/field_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs similarity index 100% rename from frontend/rust-lib/flowy-grid/src/services/entities/field_entities.rs rename to frontend/rust-lib/flowy-grid/src/entities/field_entities.rs diff --git a/frontend/rust-lib/flowy-grid/src/services/entities/mod.rs b/frontend/rust-lib/flowy-grid/src/entities/mod.rs similarity index 100% rename from frontend/rust-lib/flowy-grid/src/services/entities/mod.rs rename to frontend/rust-lib/flowy-grid/src/entities/mod.rs diff --git a/frontend/rust-lib/flowy-grid/src/services/entities/row_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/row_entities.rs similarity index 100% rename from frontend/rust-lib/flowy-grid/src/services/entities/row_entities.rs rename to frontend/rust-lib/flowy-grid/src/entities/row_entities.rs diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index 6e34361961..7374515793 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -1,14 +1,13 @@ +use crate::entities::*; use crate::manager::GridManager; -use crate::services::entities::*; use crate::services::field::type_options::*; use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_json_str}; -use crate::services::grid_editor::ClientGridEditor; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_grid_data_model::entities::*; use lib_dispatch::prelude::{data_result, AppData, Data, DataResult}; use std::sync::Arc; -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_grid_data_handler( data: Data, manager: AppData>, @@ -35,7 +34,7 @@ pub(crate) async fn get_grid_blocks_handler( data_result(repeated_grid_block) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_fields_handler( data: Data, manager: AppData>, @@ -48,7 +47,7 @@ pub(crate) async fn get_fields_handler( data_result(repeated_field) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn update_field_handler( data: Data, manager: AppData>, @@ -59,7 +58,7 @@ pub(crate) async fn update_field_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn insert_field_handler( data: Data, manager: AppData>, @@ -70,7 +69,7 @@ pub(crate) async fn insert_field_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn update_field_type_option_handler( data: Data, manager: AppData>, @@ -83,7 +82,7 @@ pub(crate) async fn update_field_type_option_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn delete_field_handler( data: Data, manager: AppData>, @@ -94,25 +93,34 @@ pub(crate) async fn delete_field_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn switch_to_field_handler( data: Data, manager: AppData>, -) -> DataResult { +) -> DataResult { let params: EditFieldParams = data.into_inner().try_into()?; - if params.field_id.is_none() { - return Err(ErrorCode::FieldIdIsEmpty.into()); - } - let field_id = params.field_id.unwrap(); let editor = manager.get_grid_editor(¶ms.grid_id)?; - editor.switch_to_field_type(&field_id, ¶ms.field_type).await?; - let field_meta = editor.get_field_meta(&field_id).await; - let edit_context = - make_edit_field_context(¶ms.grid_id, Some(field_id), params.field_type, editor, field_meta).await?; - data_result(edit_context) + editor + .switch_to_field_type(¶ms.field_id, ¶ms.field_type) + .await?; + + // Get the FieldMeta with field_id, if it doesn't exist, we create the default FieldMeta from the FieldType. + let field_meta = editor + .get_field_meta(¶ms.field_id) + .await + .unwrap_or(editor.next_field_meta(¶ms.field_type).await?); + + let type_option_data = get_type_option_data(&field_meta, ¶ms.field_type).await?; + let data = FieldTypeOptionData { + grid_id: params.grid_id, + field: field_meta.into(), + type_option_data, + }; + + data_result(data) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn duplicate_field_handler( data: Data, manager: AppData>, @@ -123,36 +131,47 @@ pub(crate) async fn duplicate_field_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] -pub(crate) async fn get_field_context_handler( - data: Data, - manager: AppData>, -) -> DataResult { - let params: EditFieldParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; - let edit_context = - make_edit_field_context(¶ms.grid_id, params.field_id, params.field_type, editor, None).await?; - - data_result(edit_context) -} - -#[tracing::instrument(level = "debug", skip(data, manager), err)] +/// Return the FieldTypeOptionData if the Field exists otherwise return record not found error. +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_field_type_option_data_handler( data: Data, manager: AppData>, ) -> DataResult { let params: EditFieldParams = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; - let field_meta = get_or_create_field_meta(params.field_id, ¶ms.field_type, editor).await?; + match editor.get_field_meta(¶ms.field_id).await { + None => Err(FlowyError::record_not_found()), + Some(field_meta) => { + let type_option_data = get_type_option_data(&field_meta, &field_meta.field_type).await?; + let data = FieldTypeOptionData { + grid_id: params.grid_id, + field: field_meta.into(), + type_option_data, + }; + data_result(data) + } + } +} + +/// Create FieldMeta and save it. Return the FieldTypeOptionData. +#[tracing::instrument(level = "trace", skip(data, manager), err)] +pub(crate) async fn create_field_type_option_data_handler( + data: Data, + manager: AppData>, +) -> DataResult { + let params: CreateFieldParams = data.into_inner().try_into()?; + let editor = manager.get_grid_editor(¶ms.grid_id)?; + let field_meta = editor.create_next_field_meta(¶ms.field_type).await?; let type_option_data = get_type_option_data(&field_meta, &field_meta.field_type).await?; data_result(FieldTypeOptionData { - field_id: field_meta.id.clone(), + grid_id: params.grid_id, + field: field_meta.into(), type_option_data, }) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn move_item_handler( data: Data, manager: AppData>, @@ -163,23 +182,7 @@ pub(crate) async fn move_item_handler( Ok(()) } -async fn make_edit_field_context( - grid_id: &str, - field_id: Option, - field_type: FieldType, - editor: Arc, - field_meta: Option, -) -> FlowyResult { - let field_meta = field_meta.unwrap_or(get_or_create_field_meta(field_id, &field_type, editor).await?); - let type_option_data = get_type_option_data(&field_meta, &field_type).await?; - let field: Field = field_meta.into(); - Ok(EditFieldContext { - grid_id: grid_id.to_string(), - grid_field: field, - type_option_data, - }) -} - +/// The FieldMeta contains multiple data, each of them belongs to a specific FieldType. async fn get_type_option_data(field_meta: &FieldMeta, field_type: &FieldType) -> FlowyResult> { let s = field_meta .get_type_option_str(field_type) @@ -190,20 +193,6 @@ async fn get_type_option_data(field_meta: &FieldMeta, field_type: &FieldType) -> Ok(type_option_data) } -async fn get_or_create_field_meta( - field_id: Option, - field_type: &FieldType, - editor: Arc, -) -> FlowyResult { - match field_id { - None => editor.create_next_field_meta(field_type).await, - Some(field_id) => match editor.get_field_meta(&field_id).await { - None => editor.create_next_field_meta(field_type).await, - Some(field_meta) => Ok(field_meta), - }, - } -} - #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn get_row_handler( data: Data, @@ -263,7 +252,7 @@ pub(crate) async fn get_cell_handler( } } -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_cell_handler( data: Data, manager: AppData>, @@ -274,28 +263,7 @@ pub(crate) async fn update_cell_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] -pub(crate) async fn get_date_cell_data_handler( - data: Data, - manager: AppData>, -) -> DataResult { - let params: CellIdentifier = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; - match editor.get_field_meta(¶ms.field_id).await { - None => { - tracing::error!("Can't find the corresponding field with id: {}", params.field_id); - data_result(DateCellData::default()) - } - Some(field_meta) => { - let cell_meta = editor.get_cell_meta(¶ms.row_id, ¶ms.field_id).await?; - let type_option = DateTypeOption::from(&field_meta); - let date_cell_data = type_option.make_date_cell_data(&cell_meta)?; - data_result(date_cell_data) - } - } -} - -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn new_select_option_handler( data: Data, manager: AppData>, @@ -312,7 +280,7 @@ pub(crate) async fn new_select_option_handler( } } -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_select_option_handler( data: Data, manager: AppData>, @@ -352,7 +320,7 @@ pub(crate) async fn update_select_option_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] +#[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_select_option_handler( data: Data, manager: AppData>, @@ -361,7 +329,7 @@ pub(crate) async fn get_select_option_handler( let editor = manager.get_grid_editor(¶ms.grid_id)?; match editor.get_field_meta(¶ms.field_id).await { None => { - tracing::error!("Can't find the corresponding field with id: {}", params.field_id); + tracing::error!("Can't find the select option field with id: {}", params.field_id); data_result(SelectOptionCellData::default()) } Some(field_meta) => { @@ -373,7 +341,7 @@ pub(crate) async fn get_select_option_handler( } } -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_select_option_cell_handler( data: Data, manager: AppData>, @@ -384,7 +352,7 @@ pub(crate) async fn update_select_option_cell_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip_all, err)] +#[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_date_cell_handler( data: Data, manager: AppData>, diff --git a/frontend/rust-lib/flowy-grid/src/event_map.rs b/frontend/rust-lib/flowy-grid/src/event_map.rs index 2d29d7f096..48e5fd9b6c 100644 --- a/frontend/rust-lib/flowy-grid/src/event_map.rs +++ b/frontend/rust-lib/flowy-grid/src/event_map.rs @@ -18,9 +18,9 @@ pub fn create(grid_manager: Arc) -> Module { .event(GridEvent::DeleteField, delete_field_handler) .event(GridEvent::SwitchToField, switch_to_field_handler) .event(GridEvent::DuplicateField, duplicate_field_handler) - .event(GridEvent::GetEditFieldContext, get_field_context_handler) .event(GridEvent::MoveItem, move_item_handler) .event(GridEvent::GetFieldTypeOption, get_field_type_option_data_handler) + .event(GridEvent::CreateFieldTypeOption, create_field_type_option_data_handler) // Row .event(GridEvent::CreateRow, create_row_handler) .event(GridEvent::GetRow, get_row_handler) @@ -29,7 +29,6 @@ pub fn create(grid_manager: Arc) -> Module { // Cell .event(GridEvent::GetCell, get_cell_handler) .event(GridEvent::UpdateCell, update_cell_handler) - .event(GridEvent::GetDateCellData, get_date_cell_data_handler) // SelectOption .event(GridEvent::NewSelectOption, new_select_option_handler) .event(GridEvent::UpdateSelectOption, update_select_option_handler) @@ -65,20 +64,20 @@ pub enum GridEvent { #[event(input = "FieldIdentifierPayload")] DeleteField = 14, - #[event(input = "EditFieldPayload", output = "EditFieldContext")] + #[event(input = "EditFieldPayload", output = "FieldTypeOptionData")] SwitchToField = 20, #[event(input = "FieldIdentifierPayload")] DuplicateField = 21, - #[event(input = "EditFieldPayload", output = "EditFieldContext")] - GetEditFieldContext = 22, - #[event(input = "MoveItemPayload")] - MoveItem = 23, + MoveItem = 22, #[event(input = "EditFieldPayload", output = "FieldTypeOptionData")] - GetFieldTypeOption = 24, + GetFieldTypeOption = 23, + + #[event(input = "EditFieldPayload", output = "FieldTypeOptionData")] + CreateFieldTypeOption = 24, #[event(input = "CreateSelectOptionPayload", output = "SelectOption")] NewSelectOption = 30, @@ -112,7 +111,4 @@ pub enum GridEvent { #[event(input = "DateChangesetPayload")] UpdateDateCell = 80, - - #[event(input = "CellIdentifierPayload", output = "DateCellData")] - GetDateCellData = 90, } diff --git a/frontend/rust-lib/flowy-grid/src/lib.rs b/frontend/rust-lib/flowy-grid/src/lib.rs index a3ac3411e2..4f7605b0b8 100644 --- a/frontend/rust-lib/flowy-grid/src/lib.rs +++ b/frontend/rust-lib/flowy-grid/src/lib.rs @@ -6,6 +6,7 @@ pub mod event_map; pub mod manager; mod dart_notification; +pub mod entities; mod protobuf; pub mod services; pub mod util; diff --git a/frontend/rust-lib/flowy-grid/src/manager.rs b/frontend/rust-lib/flowy-grid/src/manager.rs index 218dff8782..2965cb5bd9 100644 --- a/frontend/rust-lib/flowy-grid/src/manager.rs +++ b/frontend/rust-lib/flowy-grid/src/manager.rs @@ -1,5 +1,5 @@ -use crate::services::grid_editor::ClientGridEditor; -use crate::services::persistence::block_index::BlockIndexPersistence; +use crate::services::grid_editor::GridMetaEditor; +use crate::services::persistence::block_index::BlockIndexCache; use crate::services::persistence::kv::GridKVPersistence; use crate::services::persistence::GridDatabase; use bytes::Bytes; @@ -20,9 +20,9 @@ pub trait GridUser: Send + Sync { } pub struct GridManager { - editor_map: Arc, + editor_map: Arc>>, grid_user: Arc, - block_index_persistence: Arc, + block_index_cache: Arc, #[allow(dead_code)] kv_persistence: Arc, } @@ -33,14 +33,14 @@ impl GridManager { _rev_web_socket: Arc, database: Arc, ) -> Self { - let grid_editors = Arc::new(GridEditorMap::new()); + let grid_editors = Arc::new(DashMap::new()); let kv_persistence = Arc::new(GridKVPersistence::new(database.clone())); - let block_index_persistence = Arc::new(BlockIndexPersistence::new(database)); + let block_index_persistence = Arc::new(BlockIndexCache::new(database)); Self { editor_map: grid_editors, grid_user, kv_persistence, - block_index_persistence, + block_index_cache: block_index_persistence, } } @@ -67,7 +67,7 @@ impl GridManager { } #[tracing::instrument(level = "debug", skip_all, fields(grid_id), err)] - pub async fn open_grid>(&self, grid_id: T) -> FlowyResult> { + pub async fn open_grid>(&self, grid_id: T) -> FlowyResult> { let grid_id = grid_id.as_ref(); tracing::Span::current().record("grid_id", &grid_id); self.get_or_create_grid_editor(grid_id).await @@ -90,23 +90,27 @@ impl GridManager { } // #[tracing::instrument(level = "debug", skip(self), err)] - pub fn get_grid_editor(&self, grid_id: &str) -> FlowyResult> { + pub fn get_grid_editor(&self, grid_id: &str) -> FlowyResult> { match self.editor_map.get(grid_id) { None => Err(FlowyError::internal().context("Should call open_grid function first")), - Some(editor) => Ok(editor), + Some(editor) => Ok(editor.clone()), } } - async fn get_or_create_grid_editor(&self, grid_id: &str) -> FlowyResult> { + async fn get_or_create_grid_editor(&self, grid_id: &str) -> FlowyResult> { match self.editor_map.get(grid_id) { None => { tracing::trace!("Create grid editor with id: {}", grid_id); let db_pool = self.grid_user.db_pool()?; let editor = self.make_grid_editor(grid_id, db_pool).await?; - self.editor_map.insert(grid_id, &editor); + + if self.editor_map.contains_key(grid_id) { + tracing::warn!("Grid:{} already exists in cache", grid_id); + } + self.editor_map.insert(grid_id.to_string(), editor.clone()); Ok(editor) } - Some(editor) => Ok(editor), + Some(editor) => Ok(editor.clone()), } } @@ -115,11 +119,10 @@ impl GridManager { &self, grid_id: &str, pool: Arc, - ) -> Result, FlowyError> { + ) -> Result, FlowyError> { let user = self.grid_user.clone(); let rev_manager = self.make_grid_rev_manager(grid_id, pool.clone())?; - let grid_editor = - ClientGridEditor::new(grid_id, user, rev_manager, self.block_index_persistence.clone()).await?; + let grid_editor = GridMetaEditor::new(grid_id, user, rev_manager, self.block_index_cache.clone()).await?; Ok(grid_editor) } @@ -145,42 +148,16 @@ impl GridManager { } } -pub struct GridEditorMap { - inner: DashMap>, -} - -impl GridEditorMap { - fn new() -> Self { - Self { inner: DashMap::new() } - } - - pub(crate) fn insert(&self, grid_id: &str, grid_editor: &Arc) { - if self.inner.contains_key(grid_id) { - tracing::warn!("Grid:{} already exists in cache", grid_id); - } - self.inner.insert(grid_id.to_string(), grid_editor.clone()); - } - - pub(crate) fn get(&self, grid_id: &str) -> Option> { - Some(self.inner.get(grid_id)?.clone()) - } - - pub(crate) fn remove(&self, grid_id: &str) { - self.inner.remove(grid_id); - } -} - pub async fn make_grid_view_data( user_id: &str, view_id: &str, 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 @@ -190,21 +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_persistence - .insert_or_update(&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/event_map.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs index bb7494ec3e..73eaff920f 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs +++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs @@ -34,9 +34,9 @@ pub enum GridEvent { DeleteField = 14, SwitchToField = 20, DuplicateField = 21, - GetEditFieldContext = 22, - MoveItem = 23, - GetFieldTypeOption = 24, + MoveItem = 22, + GetFieldTypeOption = 23, + CreateFieldTypeOption = 24, NewSelectOption = 30, GetSelectOptionCellData = 31, UpdateSelectOption = 32, @@ -48,7 +48,6 @@ pub enum GridEvent { UpdateCell = 71, UpdateSelectOptionCell = 72, UpdateDateCell = 80, - GetDateCellData = 90, } impl ::protobuf::ProtobufEnum for GridEvent { @@ -67,9 +66,9 @@ impl ::protobuf::ProtobufEnum for GridEvent { 14 => ::std::option::Option::Some(GridEvent::DeleteField), 20 => ::std::option::Option::Some(GridEvent::SwitchToField), 21 => ::std::option::Option::Some(GridEvent::DuplicateField), - 22 => ::std::option::Option::Some(GridEvent::GetEditFieldContext), - 23 => ::std::option::Option::Some(GridEvent::MoveItem), - 24 => ::std::option::Option::Some(GridEvent::GetFieldTypeOption), + 22 => ::std::option::Option::Some(GridEvent::MoveItem), + 23 => ::std::option::Option::Some(GridEvent::GetFieldTypeOption), + 24 => ::std::option::Option::Some(GridEvent::CreateFieldTypeOption), 30 => ::std::option::Option::Some(GridEvent::NewSelectOption), 31 => ::std::option::Option::Some(GridEvent::GetSelectOptionCellData), 32 => ::std::option::Option::Some(GridEvent::UpdateSelectOption), @@ -81,7 +80,6 @@ impl ::protobuf::ProtobufEnum for GridEvent { 71 => ::std::option::Option::Some(GridEvent::UpdateCell), 72 => ::std::option::Option::Some(GridEvent::UpdateSelectOptionCell), 80 => ::std::option::Option::Some(GridEvent::UpdateDateCell), - 90 => ::std::option::Option::Some(GridEvent::GetDateCellData), _ => ::std::option::Option::None } } @@ -97,9 +95,9 @@ impl ::protobuf::ProtobufEnum for GridEvent { GridEvent::DeleteField, GridEvent::SwitchToField, GridEvent::DuplicateField, - GridEvent::GetEditFieldContext, GridEvent::MoveItem, GridEvent::GetFieldTypeOption, + GridEvent::CreateFieldTypeOption, GridEvent::NewSelectOption, GridEvent::GetSelectOptionCellData, GridEvent::UpdateSelectOption, @@ -111,7 +109,6 @@ impl ::protobuf::ProtobufEnum for GridEvent { GridEvent::UpdateCell, GridEvent::UpdateSelectOptionCell, GridEvent::UpdateDateCell, - GridEvent::GetDateCellData, ]; values } @@ -140,18 +137,18 @@ impl ::protobuf::reflect::ProtobufValue for GridEvent { } static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x0fevent_map.proto*\xda\x03\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\ + \n\x0fevent_map.proto*\xc7\x03\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\ \0\x12\x11\n\rGetGridBlocks\x10\x01\x12\r\n\tGetFields\x10\n\x12\x0f\n\ \x0bUpdateField\x10\x0b\x12\x19\n\x15UpdateFieldTypeOption\x10\x0c\x12\ \x0f\n\x0bInsertField\x10\r\x12\x0f\n\x0bDeleteField\x10\x0e\x12\x11\n\r\ - SwitchToField\x10\x14\x12\x12\n\x0eDuplicateField\x10\x15\x12\x17\n\x13G\ - etEditFieldContext\x10\x16\x12\x0c\n\x08MoveItem\x10\x17\x12\x16\n\x12Ge\ - tFieldTypeOption\x10\x18\x12\x13\n\x0fNewSelectOption\x10\x1e\x12\x1b\n\ - \x17GetSelectOptionCellData\x10\x1f\x12\x16\n\x12UpdateSelectOption\x10\ - \x20\x12\r\n\tCreateRow\x102\x12\n\n\x06GetRow\x103\x12\r\n\tDeleteRow\ - \x104\x12\x10\n\x0cDuplicateRow\x105\x12\x0b\n\x07GetCell\x10F\x12\x0e\n\ - \nUpdateCell\x10G\x12\x1a\n\x16UpdateSelectOptionCell\x10H\x12\x12\n\x0e\ - UpdateDateCell\x10P\x12\x13\n\x0fGetDateCellData\x10Zb\x06proto3\ + SwitchToField\x10\x14\x12\x12\n\x0eDuplicateField\x10\x15\x12\x0c\n\x08M\ + oveItem\x10\x16\x12\x16\n\x12GetFieldTypeOption\x10\x17\x12\x19\n\x15Cre\ + ateFieldTypeOption\x10\x18\x12\x13\n\x0fNewSelectOption\x10\x1e\x12\x1b\ + \n\x17GetSelectOptionCellData\x10\x1f\x12\x16\n\x12UpdateSelectOption\ + \x10\x20\x12\r\n\tCreateRow\x102\x12\n\n\x06GetRow\x103\x12\r\n\tDeleteR\ + ow\x104\x12\x10\n\x0cDuplicateRow\x105\x12\x0b\n\x07GetCell\x10F\x12\x0e\ + \n\nUpdateCell\x10G\x12\x1a\n\x16UpdateSelectOptionCell\x10H\x12\x12\n\ + \x0eUpdateDateCell\x10Pb\x06proto3\ "; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/model/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 99d0ecd1b6..8c29b9015a 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs @@ -19,9 +19,15 @@ pub use row_entities::*; mod cell_entities; pub use cell_entities::*; +mod url_type_option; +pub use url_type_option::*; + mod checkbox_type_option; pub use checkbox_type_option::*; +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/model/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/text_type_option.rs index febc180e03..b6bb5e55ab 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/model/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/text_type_option.rs @@ -26,7 +26,7 @@ #[derive(PartialEq,Clone,Default)] pub struct RichTextTypeOption { // message fields - pub format: ::std::string::String, + pub data: ::std::string::String, // special fields pub unknown_fields: ::protobuf::UnknownFields, pub cached_size: ::protobuf::CachedSize, @@ -43,30 +43,30 @@ impl RichTextTypeOption { ::std::default::Default::default() } - // string format = 1; + // string data = 1; - pub fn get_format(&self) -> &str { - &self.format + pub fn get_data(&self) -> &str { + &self.data } - pub fn clear_format(&mut self) { - self.format.clear(); + pub fn clear_data(&mut self) { + self.data.clear(); } // Param is passed by value, moved - pub fn set_format(&mut self, v: ::std::string::String) { - self.format = v; + pub fn set_data(&mut self, v: ::std::string::String) { + self.data = v; } // Mutable pointer to the field. // If field is not initialized, it is initialized with default value first. - pub fn mut_format(&mut self) -> &mut ::std::string::String { - &mut self.format + pub fn mut_data(&mut self) -> &mut ::std::string::String { + &mut self.data } // Take field - pub fn take_format(&mut self) -> ::std::string::String { - ::std::mem::replace(&mut self.format, ::std::string::String::new()) + pub fn take_data(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.data, ::std::string::String::new()) } } @@ -80,7 +80,7 @@ impl ::protobuf::Message for RichTextTypeOption { let (field_number, wire_type) = is.read_tag_unpack()?; match field_number { 1 => { - ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.format)?; + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?; }, _ => { ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; @@ -94,8 +94,8 @@ impl ::protobuf::Message for RichTextTypeOption { #[allow(unused_variables)] fn compute_size(&self) -> u32 { let mut my_size = 0; - if !self.format.is_empty() { - my_size += ::protobuf::rt::string_size(1, &self.format); + if !self.data.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.data); } my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); self.cached_size.set(my_size); @@ -103,8 +103,8 @@ impl ::protobuf::Message for RichTextTypeOption { } fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> { - if !self.format.is_empty() { - os.write_string(1, &self.format)?; + if !self.data.is_empty() { + os.write_string(1, &self.data)?; } os.write_unknown_fields(self.get_unknown_fields())?; ::std::result::Result::Ok(()) @@ -145,9 +145,9 @@ impl ::protobuf::Message for RichTextTypeOption { descriptor.get(|| { let mut fields = ::std::vec::Vec::new(); fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( - "format", - |m: &RichTextTypeOption| { &m.format }, - |m: &mut RichTextTypeOption| { &mut m.format }, + "data", + |m: &RichTextTypeOption| { &m.data }, + |m: &mut RichTextTypeOption| { &mut m.data }, )); ::protobuf::reflect::MessageDescriptor::new_pb_name::( "RichTextTypeOption", @@ -165,7 +165,7 @@ impl ::protobuf::Message for RichTextTypeOption { impl ::protobuf::Clear for RichTextTypeOption { fn clear(&mut self) { - self.format.clear(); + self.data.clear(); self.unknown_fields.clear(); } } @@ -183,8 +183,8 @@ impl ::protobuf::reflect::ProtobufValue for RichTextTypeOption { } static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x16text_type_option.proto\",\n\x12RichTextTypeOption\x12\x16\n\x06for\ - mat\x18\x01\x20\x01(\tR\x06formatb\x06proto3\ + \n\x16text_type_option.proto\"(\n\x12RichTextTypeOption\x12\x12\n\x04dat\ + a\x18\x01\x20\x01(\tR\x04datab\x06proto3\ "; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/model/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/url_type_option.rs new file mode 100644 index 0000000000..fe83999fd3 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/url_type_option.rs @@ -0,0 +1,403 @@ +// This file is generated by rust-protobuf 2.25.2. Do not edit +// @generated + +// https://github.com/rust-lang/rust-clippy/issues/702 +#![allow(unknown_lints)] +#![allow(clippy::all)] + +#![allow(unused_attributes)] +#![cfg_attr(rustfmt, rustfmt::skip)] + +#![allow(box_pointers)] +#![allow(dead_code)] +#![allow(missing_docs)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(trivial_casts)] +#![allow(unused_imports)] +#![allow(unused_results)] +//! Generated file from `url_type_option.proto` + +/// Generated files are compatible only with the same version +/// of protobuf runtime. +// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2; + +#[derive(PartialEq,Clone,Default)] +pub struct URLTypeOption { + // message fields + pub data: ::std::string::String, + // special fields + pub unknown_fields: ::protobuf::UnknownFields, + pub cached_size: ::protobuf::CachedSize, +} + +impl<'a> ::std::default::Default for &'a URLTypeOption { + fn default() -> &'a URLTypeOption { + ::default_instance() + } +} + +impl URLTypeOption { + pub fn new() -> URLTypeOption { + ::std::default::Default::default() + } + + // string data = 1; + + + pub fn get_data(&self) -> &str { + &self.data + } + pub fn clear_data(&mut self) { + self.data.clear(); + } + + // Param is passed by value, moved + pub fn set_data(&mut self, v: ::std::string::String) { + self.data = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_data(&mut self) -> &mut ::std::string::String { + &mut self.data + } + + // Take field + pub fn take_data(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.data, ::std::string::String::new()) + } +} + +impl ::protobuf::Message for URLTypeOption { + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> { + while !is.eof()? { + let (field_number, wire_type) = is.read_tag_unpack()?; + match field_number { + 1 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?; + }, + _ => { + ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u32 { + let mut my_size = 0; + if !self.data.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.data); + } + my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); + self.cached_size.set(my_size); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> { + if !self.data.is_empty() { + os.write_string(1, &self.data)?; + } + os.write_unknown_fields(self.get_unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn get_cached_size(&self) -> u32 { + self.cached_size.get() + } + + fn get_unknown_fields(&self) -> &::protobuf::UnknownFields { + &self.unknown_fields + } + + fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields { + &mut self.unknown_fields + } + + fn as_any(&self) -> &dyn (::std::any::Any) { + self as &dyn (::std::any::Any) + } + fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) { + self as &mut dyn (::std::any::Any) + } + fn into_any(self: ::std::boxed::Box) -> ::std::boxed::Box { + self + } + + fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor { + Self::descriptor_static() + } + + fn new() -> URLTypeOption { + URLTypeOption::new() + } + + fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT; + descriptor.get(|| { + let mut fields = ::std::vec::Vec::new(); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "data", + |m: &URLTypeOption| { &m.data }, + |m: &mut URLTypeOption| { &mut m.data }, + )); + ::protobuf::reflect::MessageDescriptor::new_pb_name::( + "URLTypeOption", + fields, + file_descriptor_proto() + ) + }) + } + + fn default_instance() -> &'static URLTypeOption { + static instance: ::protobuf::rt::LazyV2 = ::protobuf::rt::LazyV2::INIT; + instance.get(URLTypeOption::new) + } +} + +impl ::protobuf::Clear for URLTypeOption { + fn clear(&mut self) { + self.data.clear(); + self.unknown_fields.clear(); + } +} + +impl ::std::fmt::Debug for URLTypeOption { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for URLTypeOption { + fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef { + ::protobuf::reflect::ReflectValueRef::Message(self) + } +} + +#[derive(PartialEq,Clone,Default)] +pub struct URLCellData { + // message fields + pub url: ::std::string::String, + pub content: ::std::string::String, + // special fields + pub unknown_fields: ::protobuf::UnknownFields, + pub cached_size: ::protobuf::CachedSize, +} + +impl<'a> ::std::default::Default for &'a URLCellData { + fn default() -> &'a URLCellData { + ::default_instance() + } +} + +impl URLCellData { + pub fn new() -> URLCellData { + ::std::default::Default::default() + } + + // string url = 1; + + + pub fn get_url(&self) -> &str { + &self.url + } + pub fn clear_url(&mut self) { + self.url.clear(); + } + + // Param is passed by value, moved + pub fn set_url(&mut self, v: ::std::string::String) { + self.url = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_url(&mut self) -> &mut ::std::string::String { + &mut self.url + } + + // Take field + pub fn take_url(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.url, ::std::string::String::new()) + } + + // string content = 2; + + + pub fn get_content(&self) -> &str { + &self.content + } + pub fn clear_content(&mut self) { + self.content.clear(); + } + + // Param is passed by value, moved + pub fn set_content(&mut self, v: ::std::string::String) { + self.content = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_content(&mut self) -> &mut ::std::string::String { + &mut self.content + } + + // Take field + pub fn take_content(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.content, ::std::string::String::new()) + } +} + +impl ::protobuf::Message for URLCellData { + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> { + while !is.eof()? { + let (field_number, wire_type) = is.read_tag_unpack()?; + match field_number { + 1 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.url)?; + }, + 2 => { + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.content)?; + }, + _ => { + ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u32 { + let mut my_size = 0; + if !self.url.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.url); + } + if !self.content.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.content); + } + my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); + self.cached_size.set(my_size); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> { + if !self.url.is_empty() { + os.write_string(1, &self.url)?; + } + if !self.content.is_empty() { + os.write_string(2, &self.content)?; + } + os.write_unknown_fields(self.get_unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn get_cached_size(&self) -> u32 { + self.cached_size.get() + } + + fn get_unknown_fields(&self) -> &::protobuf::UnknownFields { + &self.unknown_fields + } + + fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields { + &mut self.unknown_fields + } + + fn as_any(&self) -> &dyn (::std::any::Any) { + self as &dyn (::std::any::Any) + } + fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) { + self as &mut dyn (::std::any::Any) + } + fn into_any(self: ::std::boxed::Box) -> ::std::boxed::Box { + self + } + + fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor { + Self::descriptor_static() + } + + fn new() -> URLCellData { + URLCellData::new() + } + + fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT; + descriptor.get(|| { + let mut fields = ::std::vec::Vec::new(); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "url", + |m: &URLCellData| { &m.url }, + |m: &mut URLCellData| { &mut m.url }, + )); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + "content", + |m: &URLCellData| { &m.content }, + |m: &mut URLCellData| { &mut m.content }, + )); + ::protobuf::reflect::MessageDescriptor::new_pb_name::( + "URLCellData", + fields, + file_descriptor_proto() + ) + }) + } + + fn default_instance() -> &'static URLCellData { + static instance: ::protobuf::rt::LazyV2 = ::protobuf::rt::LazyV2::INIT; + instance.get(URLCellData::new) + } +} + +impl ::protobuf::Clear for URLCellData { + fn clear(&mut self) { + self.url.clear(); + self.content.clear(); + self.unknown_fields.clear(); + } +} + +impl ::std::fmt::Debug for URLCellData { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for URLCellData { + fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef { + ::protobuf::reflect::ReflectValueRef::Message(self) + } +} + +static file_descriptor_proto_data: &'static [u8] = b"\ + \n\x15url_type_option.proto\"#\n\rURLTypeOption\x12\x12\n\x04data\x18\ + \x01\x20\x01(\tR\x04data\"9\n\x0bURLCellData\x12\x10\n\x03url\x18\x01\ + \x20\x01(\tR\x03url\x12\x18\n\x07content\x18\x02\x20\x01(\tR\x07contentb\ + \x06proto3\ +"; + +static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; + +fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto { + ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap() +} + +pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto { + file_descriptor_proto_lazy.get(|| { + parse_descriptor_proto() + }) +} diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto b/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto index 02f621b29e..623bf4b7da 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto +++ b/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto @@ -10,9 +10,9 @@ enum GridEvent { DeleteField = 14; SwitchToField = 20; DuplicateField = 21; - GetEditFieldContext = 22; - MoveItem = 23; - GetFieldTypeOption = 24; + MoveItem = 22; + GetFieldTypeOption = 23; + CreateFieldTypeOption = 24; NewSelectOption = 30; GetSelectOptionCellData = 31; UpdateSelectOption = 32; @@ -24,5 +24,4 @@ enum GridEvent { UpdateCell = 71; UpdateSelectOptionCell = 72; UpdateDateCell = 80; - GetDateCellData = 90; } diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/proto/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/protobuf/proto/text_type_option.proto b/frontend/rust-lib/flowy-grid/src/protobuf/proto/text_type_option.proto index 67cfb438ea..827c569a74 100644 --- a/frontend/rust-lib/flowy-grid/src/protobuf/proto/text_type_option.proto +++ b/frontend/rust-lib/flowy-grid/src/protobuf/proto/text_type_option.proto @@ -1,5 +1,5 @@ syntax = "proto3"; message RichTextTypeOption { - string format = 1; + string data = 1; } diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/proto/url_type_option.proto b/frontend/rust-lib/flowy-grid/src/protobuf/proto/url_type_option.proto new file mode 100644 index 0000000000..edf1c7e341 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/protobuf/proto/url_type_option.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +message URLTypeOption { + string data = 1; +} +message URLCellData { + string url = 1; + string content = 2; +} diff --git a/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs b/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs index ce7c5c8e8d..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; @@ -8,18 +8,17 @@ use flowy_sync::util::make_delta_from_revisions; use lib_infra::future::FutureResult; use lib_ot::core::PlainTextAttributes; use std::borrow::Cow; - use std::sync::Arc; use tokio::sync::RwLock; -pub struct ClientGridBlockMetaEditor { +pub struct GridBlockMetaEditor { user_id: String, pub block_id: String, pad: Arc>, rev_manager: Arc, } -impl ClientGridBlockMetaEditor { +impl GridBlockMetaEditor { pub async fn new( user_id: &str, token: &str, @@ -42,6 +41,10 @@ impl ClientGridBlockMetaEditor { }) } + 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 229afe75a2..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 @@ -1,7 +1,7 @@ use crate::dart_notification::{send_dart_notification, GridNotification}; use crate::manager::GridUser; -use crate::services::block_meta_editor::ClientGridBlockMetaEditor; -use crate::services::persistence::block_index::BlockIndexPersistence; +use crate::services::block_meta_editor::GridBlockMetaEditor; +use crate::services::persistence::block_index::BlockIndexCache; use crate::services::row::{group_row_orders, GridBlockSnapshot}; use dashmap::DashMap; use flowy_error::FlowyResult; @@ -15,20 +15,20 @@ use std::borrow::Cow; use std::collections::HashMap; use std::sync::Arc; -pub(crate) struct GridBlockMetaEditorManager { +type BlockId = String; +pub(crate) struct GridBlockManager { grid_id: String, user: Arc, - // Key: block_id - editor_map: DashMap>, - persistence: Arc, + persistence: Arc, + block_editor_map: DashMap>, } -impl GridBlockMetaEditorManager { +impl GridBlockManager { pub(crate) async fn new( grid_id: &str, user: &Arc, blocks: Vec, - persistence: Arc, + persistence: Arc, ) -> FlowyResult { let editor_map = make_block_meta_editor_map(user, blocks).await?; let user = user.clone(); @@ -36,27 +36,27 @@ impl GridBlockMetaEditorManager { let manager = Self { grid_id, user, - editor_map, + block_editor_map: editor_map, persistence, }; Ok(manager) } // #[tracing::instrument(level = "trace", skip(self))] - pub(crate) async fn get_editor(&self, block_id: &str) -> FlowyResult> { + pub(crate) async fn get_editor(&self, block_id: &str) -> FlowyResult> { debug_assert!(!block_id.is_empty()); - match self.editor_map.get(block_id) { + match self.block_editor_map.get(block_id) { None => { - tracing::error!("The is a fatal error, block is not exist"); + 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.editor_map.insert(block_id.to_owned(), editor.clone()); + self.block_editor_map.insert(block_id.to_owned(), editor.clone()); Ok(editor) } Some(editor) => Ok(editor.clone()), } } - async fn get_editor_from_row_id(&self, row_id: &str) -> FlowyResult> { + async fn get_editor_from_row_id(&self, row_id: &str) -> FlowyResult> { let block_id = self.persistence.get_block_id(row_id)?; Ok(self.get_editor(&block_id).await?) } @@ -67,7 +67,7 @@ impl GridBlockMetaEditorManager { row_meta: RowMeta, start_row_id: Option, ) -> FlowyResult { - let _ = self.persistence.insert_or_update(&row_meta.block_id, &row_meta.id)?; + let _ = self.persistence.insert(&row_meta.block_id, &row_meta.id)?; let editor = self.get_editor(&row_meta.block_id).await?; let mut index_row_order = IndexRowOrder::from(&row_meta); @@ -90,7 +90,7 @@ impl GridBlockMetaEditorManager { let editor = self.get_editor(&block_id).await?; let mut row_count = 0; for row in row_metas { - let _ = self.persistence.insert_or_update(&row.block_id, &row.id)?; + let _ = self.persistence.insert(&row.block_id, &row.id)?; let mut row_order = IndexRowOrder::from(&row); let (count, index) = editor.create_row(row, None).await?; row_count = count; @@ -256,7 +256,7 @@ impl GridBlockMetaEditorManager { async fn make_block_meta_editor_map( user: &Arc, blocks: Vec, -) -> FlowyResult>> { +) -> FlowyResult>> { let editor_map = DashMap::new(); for block in blocks { let editor = make_block_meta_editor(user, &block.block_id).await?; @@ -266,7 +266,8 @@ async fn make_block_meta_editor_map( Ok(editor_map) } -async fn make_block_meta_editor(user: &Arc, block_id: &str) -> FlowyResult { +async fn make_block_meta_editor(user: &Arc, block_id: &str) -> FlowyResult { + tracing::trace!("Open block:{} meta editor", block_id); let token = user.token()?; let user_id = user.user_id()?; let pool = user.db_pool()?; @@ -274,5 +275,5 @@ async fn make_block_meta_editor(user: &Arc, block_id: &str) -> Flo let disk_cache = Arc::new(SQLiteGridBlockMetaRevisionPersistence::new(&user_id, pool)); let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, block_id, disk_cache)); let rev_manager = RevisionManager::new(&user_id, block_id, rev_persistence); - ClientGridBlockMetaEditor::new(&user_id, &token, block_id, rev_manager).await + GridBlockMetaEditor::new(&user_id, &token, block_id, rev_manager).await } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs index 5eaabb0294..7978323be1 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs @@ -94,6 +94,7 @@ pub fn default_type_option_builder_from_type(field_type: &FieldType) -> Box SingleSelectTypeOption::default().into(), FieldType::MultiSelect => MultiSelectTypeOption::default().into(), FieldType::Checkbox => CheckboxTypeOption::default().into(), + FieldType::URL => URLTypeOption::default().into(), }; type_option_builder_from_json_str(&s, field_type) @@ -107,6 +108,7 @@ pub fn type_option_builder_from_json_str(s: &str, field_type: &FieldType) -> Box FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_json_str(s)), FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_json_str(s)), FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_json_str(s)), + FieldType::URL => Box::new(URLTypeOptionBuilder::from_json_str(s)), } } @@ -119,5 +121,6 @@ pub fn type_option_builder_from_bytes>(bytes: T, field_type: &Fie FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_protobuf_bytes(bytes)), FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_protobuf_bytes(bytes)), FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_protobuf_bytes(bytes)), + FieldType::URL => Box::new(URLTypeOptionBuilder::from_protobuf_bytes(bytes)), } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs index 73c3f5f2d7..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 @@ -1,15 +1,14 @@ use crate::impl_type_option; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData}; +use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData}; use bytes::Bytes; use flowy_derive::ProtoBuf; -use flowy_error::FlowyError; +use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{ CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, }; use serde::{Deserialize, Serialize}; -use std::str::FromStr; #[derive(Default)] pub struct CheckboxTypeOptionBuilder(CheckboxTypeOption); @@ -43,32 +42,38 @@ impl_type_option!(CheckboxTypeOption, FieldType::Checkbox); const YES: &str = "Yes"; const NO: &str = "No"; -impl CellDataOperation for CheckboxTypeOption { - fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - if !type_option_cell_data.is_checkbox() { - return DecodedCellData::default(); - } - let cell_data = type_option_cell_data.data; - if cell_data == YES || cell_data == NO { - return DecodedCellData::from_content(cell_data); - } +impl CellDataOperation for CheckboxTypeOption { + fn decode_cell_data( + &self, + encoded_data: T, + decoded_field_type: &FieldType, + _field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into, + { + if !decoded_field_type.is_checkbox() { + return Ok(DecodedCellData::default()); } - DecodedCellData::default() + let encoded_data = encoded_data.into(); + if encoded_data == YES || encoded_data == NO { + return Ok(DecodedCellData::new(encoded_data)); + } + + Ok(DecodedCellData::default()) } - fn apply_changeset>( - &self, - changeset: T, - _cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { let changeset = changeset.into(); let s = match string_to_bool(&changeset) { true => YES, false => NO, }; - Ok(TypeOptionCellData::new(s, self.field_type()).json()) + Ok(s.to_string()) } } @@ -88,32 +93,49 @@ fn string_to_bool(bool_str: &str) -> bool { #[cfg(test)] mod tests { use crate::services::field::type_options::checkbox_type_option::{NO, YES}; - use crate::services::field::CheckboxTypeOption; + use crate::services::field::FieldBuilder; - use crate::services::row::CellDataOperation; + use crate::services::row::{apply_cell_data_changeset, decode_cell_data_from_type_option_cell_data}; + use flowy_grid_data_model::entities::FieldType; #[test] fn checkout_box_description_test() { - let type_option = CheckboxTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); + let field_meta = FieldBuilder::from_field_type(&FieldType::Checkbox).build(); + let data = apply_cell_data_changeset("true", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), + YES + ); - let data = type_option.apply_changeset("true", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES); + let data = apply_cell_data_changeset("1", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), + YES + ); - let data = type_option.apply_changeset("1", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES); + let data = apply_cell_data_changeset("yes", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), + YES + ); - let data = type_option.apply_changeset("yes", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, YES); + let data = apply_cell_data_changeset("false", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), + NO + ); - let data = type_option.apply_changeset("false", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO); + let data = apply_cell_data_changeset("no", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), + NO + ); - let data = type_option.apply_changeset("no", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO); - - let data = type_option.apply_changeset("123", None).unwrap(); - assert_eq!(type_option.decode_cell_data(data, &field_meta).content, NO); + let data = apply_cell_data_changeset("12", None, &field_meta).unwrap(); + assert_eq!( + decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).to_string(), + NO + ); } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs index 84cfe0e4bf..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::entities::{CellIdentifier, CellIdentifierPayload}; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData}; +use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData}; use bytes::Bytes; use 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,34 +77,6 @@ impl DateTypeOption { } } - pub fn make_date_cell_data(&self, cell_meta: &Option) -> FlowyResult { - if cell_meta.is_none() { - return Ok(DateCellData::default()); - } - - let json = &cell_meta.as_ref().unwrap().data; - let result = TypeOptionCellData::from_str(json); - if result.is_err() { - return Ok(DateCellData::default()); - } - - let serde_cell_data = DateCellDataSerde::from_str(&result.unwrap().data)?; - let date = self.decode_cell_data_from_timestamp(&serde_cell_data).content; - let time = serde_cell_data.time.unwrap_or("".to_owned()); - let timestamp = serde_cell_data.timestamp; - - return Ok(DateCellData { date, time, timestamp }); - } - - fn decode_cell_data_from_timestamp(&self, serde_cell_data: &DateCellDataSerde) -> DecodedCellData { - if serde_cell_data.timestamp == 0 { - return DecodedCellData::default(); - } - - let cell_content = self.today_desc_from_timestamp(serde_cell_data.timestamp, &serde_cell_data.time); - return DecodedCellData::new(serde_cell_data.timestamp.to_string(), cell_content); - } - fn timestamp_from_utc_with_time( &self, utc: &chrono::DateTime, @@ -131,49 +103,60 @@ impl DateTypeOption { } } - return Ok(utc.timestamp()); + 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 for DateTypeOption { - fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - // Return default data if the type_option_cell_data is not FieldType::DateTime. - // It happens when switching from one field to another. - // For example: - // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen. - if !type_option_cell_data.is_date() { - return DecodedCellData::default(); - } - return match DateCellDataSerde::from_str(&type_option_cell_data.data) { - Ok(serde_cell_data) => self.decode_cell_data_from_timestamp(&serde_cell_data), - Err(_) => DecodedCellData::default(), - }; +impl CellDataOperation for DateTypeOption { + fn decode_cell_data( + &self, + encoded_data: T, + decoded_field_type: &FieldType, + _field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into, + { + // Return default data if the type_option_cell_data is not FieldType::DateTime. + // It happens when switching from one field to another. + // For example: + // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen. + if !decoded_field_type.is_date() { + return Ok(DecodedCellData::default()); } - DecodedCellData::default() + let 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: T, - _cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { let content_changeset: DateCellContentChangeset = serde_json::from_str(&changeset.into())?; let cell_data = match content_changeset.date_timestamp() { - None => DateCellDataSerde::default(), + 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(TypeOptionCellData::new(cell_data.to_string(), self.field_type()).json()) + Ok(cell_data.to_string()) } } @@ -297,40 +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(default_time_str(time_format))), - } - } - - pub(crate) fn from_timestamp(timestamp: i64, time: Option) -> Self { - Self { timestamp, time } - } - - fn to_string(self) -> String { - serde_json::to_string(&self).unwrap_or("".to_string()) - } - - fn from_str(s: &str) -> FlowyResult { - serde_json::from_str::(s).map_err(internal_error) - } -} - -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)] @@ -407,210 +356,272 @@ 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, TypeOptionCellData}; - use flowy_grid_data_model::entities::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_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - assert_eq!( - "".to_owned(), - type_option.decode_cell_data("1e".to_owned(), &field_meta).content + let field_type = FieldType::DateTime; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some("1e".to_string()), + time: Some("23:00".to_owned()), + }, + &field_type, + &field_meta, + "", ); } #[test] - fn date_description_date_format_test() { + fn date_type_option_date_format_test() { let mut type_option = DateTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); + let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); for date_format in DateFormat::iter() { type_option.date_format = date_format; match date_format { DateFormat::Friendly => { - assert_eq!( - "Mar 14,2022".to_owned(), - type_option.decode_cell_data(data(1647251762), &field_meta).content - ); + assert_decode_timestamp(1647251762, &type_option, &field_meta, "Mar 14,2022"); } DateFormat::US => { - assert_eq!( - "2022/03/14".to_owned(), - type_option.decode_cell_data(data(1647251762), &field_meta).content - ); + assert_decode_timestamp(1647251762, &type_option, &field_meta, "2022/03/14"); } DateFormat::ISO => { - assert_eq!( - "2022-03-14".to_owned(), - type_option.decode_cell_data(data(1647251762), &field_meta).content - ); + assert_decode_timestamp(1647251762, &type_option, &field_meta, "2022-03-14"); } DateFormat::Local => { - assert_eq!( - "2022/03/14".to_owned(), - type_option.decode_cell_data(data(1647251762), &field_meta).content - ); + assert_decode_timestamp(1647251762, &type_option, &field_meta, "2022/03/14"); } } } } #[test] - fn date_description_time_format_test() { + fn date_type_option_time_format_test() { let mut type_option = DateTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - for time_format in TimeFormat::iter() { - type_option.time_format = time_format; - match time_format { - TimeFormat::TwentyFourHour => { - assert_eq!( - "Mar 14,2022".to_owned(), - type_option.today_desc_from_timestamp(1647251762, &None) - ); - assert_eq!( - "Mar 14,2022".to_owned(), - type_option.decode_cell_data(data(1647251762), &field_meta).content - ); - } - TimeFormat::TwelveHour => { - assert_eq!( - "Mar 14,2022".to_owned(), - type_option.today_desc_from_timestamp(1647251762, &None) - ); - assert_eq!( - "Mar 14,2022".to_owned(), - type_option.decode_cell_data(data(1647251762), &field_meta).content - ); - } - } - } - } + let field_type = FieldType::DateTime; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); - #[test] - fn date_description_time_format_test2() { - let mut type_option = DateTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); for time_format in TimeFormat::iter() { type_option.time_format = time_format; type_option.include_time = true; match time_format { TimeFormat::TwentyFourHour => { - let changeset = DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: None, - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!("May 27,2022 00:00".to_owned(), content); - - let changeset = DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: Some("23:00".to_owned()), - }; - - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!("May 27,2022 23:00".to_owned(), content); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(1653609600.to_string()), + time: None, + }, + &field_type, + &field_meta, + "May 27,2022", + ); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(1653609600.to_string()), + time: Some("23:00".to_owned()), + }, + &field_type, + &field_meta, + "May 27,2022 23:00", + ); } TimeFormat::TwelveHour => { - let changeset = DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: None, - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!("May 27,2022 12:00 AM".to_owned(), content); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(1653609600.to_string()), + time: None, + }, + &field_type, + &field_meta, + "May 27,2022", + ); + // + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(1653609600.to_string()), + time: Some("".to_owned()), + }, + &field_type, + &field_meta, + "May 27,2022", + ); - let changeset = DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: Some("".to_owned()), - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!("May 27,2022".to_owned(), content); - - let changeset = DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: Some("11:23 pm".to_owned()), - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!("May 27,2022 11:23 PM".to_owned(), content); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(1653609600.to_string()), + time: Some("11:23 pm".to_owned()), + }, + &field_type, + &field_meta, + "May 27,2022 11:23 PM", + ); } } } } #[test] - fn date_description_apply_changeset_test() { - let mut type_option = DateTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); + 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(); - let changeset = DateCellContentChangeset { - date: Some(date_timestamp.clone()), - time: None, - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result.clone(), &field_meta).content; - assert_eq!(content, "May 27,2022".to_owned()); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(date_timestamp.clone()), + time: None, + }, + &field_type, + &field_meta, + "May 27,2022", + ); type_option.include_time = true; - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!(content, "May 27,2022 00:00".to_owned()); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(date_timestamp.clone()), + time: None, + }, + &field_type, + &field_meta, + "May 27,2022", + ); - let changeset = DateCellContentChangeset { - date: Some(date_timestamp.clone()), - time: Some("1:00".to_owned()), - }; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!(content, "May 27,2022 01:00".to_owned()); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(date_timestamp.clone()), + time: Some("1:00".to_owned()), + }, + &field_type, + &field_meta, + "May 27,2022 01:00", + ); - let changeset = DateCellContentChangeset { - date: Some(date_timestamp), - time: Some("1:00 am".to_owned()), - }; type_option.time_format = TimeFormat::TwelveHour; - let result = type_option.apply_changeset(changeset, None).unwrap(); - let content = type_option.decode_cell_data(result, &field_meta).content; - assert_eq!(content, "May 27,2022 01:00 AM".to_owned()); + assert_changeset_result( + &type_option, + DateCellContentChangeset { + date: Some(date_timestamp), + time: Some("1:00 am".to_owned()), + }, + &field_type, + &field_meta, + "May 27,2022 01:00 AM", + ); } #[test] #[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::Number).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.clone()), - 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 data(s: i64) -> String { - let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(s, None)).unwrap(); - TypeOptionCellData::new(&json, FieldType::DateTime).json() + fn assert_changeset_result( + type_option: &DateTypeOption, + changeset: DateCellContentChangeset, + _field_type: &FieldType, + field_meta: &FieldMeta, + expected: &str, + ) { + let encoded_data = type_option.apply_changeset(changeset, None).unwrap(); + assert_eq!( + expected.to_owned(), + decode_cell_data(encoded_data, type_option, field_meta) + ); + } + + fn assert_decode_timestamp(timestamp: i64, type_option: &DateTypeOption, field_meta: &FieldMeta, expected: &str) { + let encoded_data = type_option + .apply_changeset( + DateCellContentChangeset { + date: Some(timestamp.to_string()), + time: None, + }, + None, + ) + .unwrap(); + + assert_eq!( + expected.to_owned(), + decode_cell_data(encoded_data, type_option, field_meta) + ); + } + + fn decode_cell_data>( + encoded_data: T, + type_option: &DateTypeOption, + field_meta: &FieldMeta, + ) -> String { + let decoded_data = type_option + .decode_cell_data(encoded_data, &FieldType::DateTime, field_meta) + .unwrap() + .parse::() + .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/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs index 2c74b2097b..3cfe390b38 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs @@ -3,6 +3,7 @@ mod date_type_option; mod number_type_option; mod selection_type_option; mod text_type_option; +mod url_type_option; mod util; pub use checkbox_type_option::*; @@ -10,3 +11,4 @@ pub use date_type_option::*; pub use number_type_option::*; pub use selection_type_option::*; pub use text_type_option::*; +pub use url_type_option::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/format.rs similarity index 51% 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 a57b056f5d..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,179 +1,16 @@ -use crate::impl_type_option; -use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData}; -use bytes::Bytes; -use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_error::FlowyError; -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, data: String, _field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - if type_option_cell_data.is_date() { - return DecodedCellData::default(); - } - - let cell_data = type_option_cell_data.data; - match self.format { - NumberFormat::Number => { - if let Ok(v) = cell_data.parse::() { - return DecodedCellData::from_content(v.to_string()); - } - - if let Ok(v) = cell_data.parse::() { - return DecodedCellData::from_content(v.to_string()); - } - - DecodedCellData::default() - } - NumberFormat::Percent => { - let content = cell_data.parse::().map_or(String::new(), |v| v.to_string()); - DecodedCellData::from_content(content) - } - _ => { - let content = self.money_from_str(&cell_data); - DecodedCellData::from_content(content) - } - } - } else { - DecodedCellData::default() - } - } - - fn apply_changeset>( - &self, - changeset: T, - _cell_meta: Option, - ) -> Result { - 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(TypeOptionCellData::new(&data, self.field_type()).json()) - } -} - -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)] @@ -606,172 +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, TypeOptionCellData}; - use flowy_grid_data_model::entities::FieldType; - use strum::IntoEnumIterator; - - #[test] - fn number_description_invalid_input_test() { - let type_option = NumberTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - assert_eq!( - "".to_owned(), - type_option.decode_cell_data(data(""), &field_meta).content - ); - assert_eq!( - "".to_owned(), - type_option.decode_cell_data(data("abc"), &field_meta).content - ); - } - - #[test] - fn number_description_test() { - let mut type_option = NumberTypeOption::default(); - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).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_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "18443".to_owned() - ); - } - NumberFormat::USD => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "$18,443".to_owned() - ); - assert_eq!( - type_option.decode_cell_data(data(""), &field_meta).content, - "".to_owned() - ); - assert_eq!( - type_option.decode_cell_data(data("abc"), &field_meta).content, - "".to_owned() - ); - } - NumberFormat::Yen => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "¥18,443".to_owned() - ); - } - NumberFormat::Yuan => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "CN¥18,443".to_owned() - ); - } - NumberFormat::EUR => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "€18.443".to_owned() - ); - } - _ => {} - } - } - } - - fn data(s: &str) -> String { - TypeOptionCellData::new(s, FieldType::Number).json() - } - - #[test] - fn number_description_scale_test() { - let mut type_option = NumberTypeOption { - scale: 1, - ..Default::default() - }; - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - - for format in NumberFormat::iter() { - type_option.format = format; - match format { - NumberFormat::Number => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "18443".to_owned() - ); - } - NumberFormat::USD => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "$1,844.3".to_owned() - ); - } - NumberFormat::Yen => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "¥1,844.3".to_owned() - ); - } - NumberFormat::EUR => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "€1.844,3".to_owned() - ); - } - _ => {} - } - } - } - - #[test] - fn number_description_sign_test() { - let mut type_option = NumberTypeOption { - sign_positive: false, - ..Default::default() - }; - let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build(); - - for format in NumberFormat::iter() { - type_option.format = format; - match format { - NumberFormat::Number => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "18443".to_owned() - ); - } - NumberFormat::USD => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "-$18,443".to_owned() - ); - } - NumberFormat::Yen => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "-¥18,443".to_owned() - ); - } - NumberFormat::EUR => { - assert_eq!( - type_option.decode_cell_data(data("18443"), &field_meta).content, - "-€18.443".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 30ecfabd9d..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 @@ -1,5 +1,5 @@ +use crate::entities::{CellIdentifier, CellIdentifierPayload}; use crate::impl_type_option; -use crate::services::entities::{CellIdentifier, CellIdentifierPayload}; use crate::services::field::type_options::util::get_cell_data; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData}; @@ -95,29 +95,38 @@ impl SelectOptionOperation for SingleSelectTypeOption { } } -impl CellDataOperation for SingleSelectTypeOption { - fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - if !type_option_cell_data.is_single_select() { - return DecodedCellData::default(); - } +impl CellDataOperation for SingleSelectTypeOption { + fn decode_cell_data( + &self, + encoded_data: T, + decoded_field_type: &FieldType, + _field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into, + { + if !decoded_field_type.is_select_option() { + return Ok(DecodedCellData::default()); + } - if let Some(option_id) = select_option_ids(type_option_cell_data.data).first() { - return match self.options.iter().find(|option| &option.id == option_id) { - None => DecodedCellData::default(), - Some(option) => DecodedCellData::from_content(option.name.clone()), - }; + let encoded_data = encoded_data.into(); + let mut cell_data = SelectOptionCellData { + options: self.options.clone(), + select_options: vec![], + }; + if let Some(option_id) = select_option_ids(encoded_data).first() { + if let Some(option) = self.options.iter().find(|option| &option.id == option_id) { + cell_data.select_options.push(option.clone()); } } - DecodedCellData::default() + DecodedCellData::try_from_bytes(cell_data) } - fn apply_changeset>( - &self, - changeset: T, - _cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { let changeset = changeset.into(); let select_option_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset)?; let new_cell_data: String; @@ -129,7 +138,7 @@ impl CellDataOperation for SingleSelectTypeOption { new_cell_data = "".to_string() } - Ok(TypeOptionCellData::new(&new_cell_data, self.field_type()).json()) + Ok(new_cell_data) } } @@ -184,31 +193,38 @@ impl SelectOptionOperation for MultiSelectTypeOption { } } -impl CellDataOperation for MultiSelectTypeOption { - fn decode_cell_data(&self, data: String, _field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - if !type_option_cell_data.is_multi_select() { - return DecodedCellData::default(); - } - let option_ids = select_option_ids(type_option_cell_data.data); - let content = self - .options - .iter() - .filter(|option| option_ids.contains(&option.id)) - .map(|option| option.name.clone()) - .collect::>() - .join(SELECTION_IDS_SEPARATOR); - DecodedCellData::from_content(content) - } else { - DecodedCellData::default() +impl CellDataOperation for MultiSelectTypeOption { + fn decode_cell_data( + &self, + encoded_data: T, + decoded_field_type: &FieldType, + _field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into, + { + if !decoded_field_type.is_select_option() { + return Ok(DecodedCellData::default()); } + + let encoded_data = encoded_data.into(); + let select_options = select_option_ids(encoded_data) + .into_iter() + .flat_map(|option_id| self.options.iter().find(|option| option.id == option_id).cloned()) + .collect::>(); + + let cell_data = SelectOptionCellData { + options: self.options.clone(), + select_options, + }; + + DecodedCellData::try_from_bytes(cell_data) } - fn apply_changeset>( - &self, - changeset: T, - cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: T, cell_meta: Option) -> Result + where + T: Into, + { let content_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset.into())?; let new_cell_data: String; match cell_meta { @@ -237,7 +253,7 @@ impl CellDataOperation for MultiSelectTypeOption { } } - Ok(TypeOptionCellData::new(&new_cell_data, self.field_type()).json()) + Ok(new_cell_data) } } @@ -268,7 +284,7 @@ fn select_option_ids(data: String) -> Vec { .collect::>() } -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, ProtoBuf)] pub struct SelectOption { #[pb(index = 1)] pub id: String, @@ -434,7 +450,7 @@ pub struct SelectOptionCellData { pub select_options: Vec, } -#[derive(ProtoBuf_Enum, Serialize, Deserialize, Debug, Clone)] +#[derive(ProtoBuf_Enum, PartialEq, Eq, Serialize, Deserialize, Debug, Clone)] #[repr(u8)] pub enum SelectOptionColor { Purple = 0, @@ -490,9 +506,10 @@ mod tests { use crate::services::field::FieldBuilder; use crate::services::field::{ MultiSelectTypeOption, MultiSelectTypeOptionBuilder, SelectOption, SelectOptionCellContentChangeset, - SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR, + SelectOptionCellData, SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR, }; use crate::services::row::CellDataOperation; + use flowy_grid_data_model::entities::FieldMeta; #[test] fn single_select_test() { @@ -514,29 +531,24 @@ mod tests { let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR); let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - type_option.decode_cell_data(cell_data, &field_meta).content, - google_option.name, - ); + assert_single_select_options(cell_data, &type_option, &field_meta, vec![google_option.clone()]); let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - type_option.decode_cell_data(cell_data, &field_meta).content, - google_option.name, - ); + assert_single_select_options(cell_data, &type_option, &field_meta, vec![google_option]); // Invalid option id let cell_data = type_option .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) .unwrap(); - assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); + assert_single_select_options(cell_data, &type_option, &field_meta, vec![]); // Invalid option id let cell_data = type_option .apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None) .unwrap(); - assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); + + assert_single_select_options(cell_data, &type_option, &field_meta, vec![]); // Invalid changeset assert!(type_option.apply_changeset("123", None).is_err()); @@ -562,31 +574,64 @@ mod tests { let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - type_option.decode_cell_data(cell_data, &field_meta).content, - vec![google_option.name.clone(), facebook_option.name].join(SELECTION_IDS_SEPARATOR), + assert_multi_select_options( + cell_data, + &type_option, + &field_meta, + vec![google_option.clone(), facebook_option], ); let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_eq!( - type_option.decode_cell_data(cell_data, &field_meta).content, - google_option.name, - ); + assert_multi_select_options(cell_data, &type_option, &field_meta, vec![google_option]); // Invalid option id let cell_data = type_option .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) .unwrap(); - assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); + assert_multi_select_options(cell_data, &type_option, &field_meta, vec![]); // Invalid option id let cell_data = type_option .apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None) .unwrap(); - assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",); + assert_multi_select_options(cell_data, &type_option, &field_meta, vec![]); // Invalid changeset assert!(type_option.apply_changeset("123", None).is_err()); } + + fn assert_multi_select_options( + cell_data: String, + type_option: &MultiSelectTypeOption, + field_meta: &FieldMeta, + expected: Vec, + ) { + assert_eq!( + expected, + type_option + .decode_cell_data(cell_data, &field_meta.field_type, field_meta) + .unwrap() + .parse::() + .unwrap() + .select_options, + ); + } + + fn assert_single_select_options( + cell_data: String, + type_option: &SingleSelectTypeOption, + field_meta: &FieldMeta, + expected: Vec, + ) { + assert_eq!( + expected, + type_option + .decode_cell_data(cell_data, &field_meta.field_type, field_meta) + .unwrap() + .parse::() + .unwrap() + .select_options, + ); + } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs index 348114e122..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 @@ -1,16 +1,13 @@ use crate::impl_type_option; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{ - decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData, -}; +use crate::services::row::{decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData}; use bytes::Bytes; use flowy_derive::ProtoBuf; -use flowy_error::FlowyError; +use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{ CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, }; use serde::{Deserialize, Serialize}; -use std::str::FromStr; #[derive(Default)] pub struct RichTextTypeOptionBuilder(RichTextTypeOption); @@ -30,37 +27,41 @@ impl TypeOptionBuilder for RichTextTypeOptionBuilder { #[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)] pub struct RichTextTypeOption { #[pb(index = 1)] - pub format: String, + data: String, //It's not used yet } impl_type_option!(RichTextTypeOption, FieldType::RichText); -impl CellDataOperation for RichTextTypeOption { - fn decode_cell_data(&self, data: String, field_meta: &FieldMeta) -> DecodedCellData { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) { - if type_option_cell_data.is_date() - || type_option_cell_data.is_single_select() - || type_option_cell_data.is_multi_select() - || type_option_cell_data.is_number() - { - decode_cell_data(data, field_meta, &type_option_cell_data.field_type).unwrap_or_default() - } else { - DecodedCellData::from_content(type_option_cell_data.data) - } +impl CellDataOperation for RichTextTypeOption { + fn decode_cell_data( + &self, + encoded_data: T, + decoded_field_type: &FieldType, + field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into, + { + if decoded_field_type.is_date() + || decoded_field_type.is_single_select() + || decoded_field_type.is_multi_select() + || decoded_field_type.is_number() + { + decode_cell_data(encoded_data, decoded_field_type, decoded_field_type, field_meta) } else { - DecodedCellData::default() + let cell_data = encoded_data.into(); + Ok(DecodedCellData::new(cell_data)) } } - fn apply_changeset>( - &self, - changeset: T, - _cell_meta: Option, - ) -> Result { + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { let data = changeset.into(); if data.len() > 10000 { Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000")) } else { - Ok(TypeOptionCellData::new(&data, self.field_type()).json()) + Ok(data.0) } } } @@ -69,7 +70,7 @@ impl CellDataOperation for RichTextTypeOption { mod tests { use crate::services::field::FieldBuilder; use crate::services::field::*; - use crate::services::row::{CellDataOperation, TypeOptionCellData}; + use crate::services::row::CellDataOperation; use flowy_grid_data_model::entities::FieldType; #[test] @@ -77,25 +78,33 @@ mod tests { let type_option = RichTextTypeOption::default(); // date - let date_time_field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(1647251762, None)).unwrap(); - let data = TypeOptionCellData::new(&json, FieldType::DateTime).json(); + let field_type = FieldType::DateTime; + let date_time_field_meta = FieldBuilder::from_field_type(&field_type).build(); + assert_eq!( - type_option.decode_cell_data(data, &date_time_field_meta).content, + type_option + .decode_cell_data(1647251762.to_string(), &field_type, &date_time_field_meta) + .unwrap() + .parse::() + .unwrap() + .date, "Mar 14,2022".to_owned() ); // Single select let done_option = SelectOption::new("Done"); let done_option_id = done_option.id.clone(); - let single_select = SingleSelectTypeOptionBuilder::default().option(done_option); + let single_select = SingleSelectTypeOptionBuilder::default().option(done_option.clone()); let single_select_field_meta = FieldBuilder::new(single_select).build(); - let cell_data = TypeOptionCellData::new(&done_option_id, FieldType::SingleSelect).json(); + assert_eq!( type_option - .decode_cell_data(cell_data, &single_select_field_meta) - .content, - "Done".to_owned() + .decode_cell_data(done_option_id, &FieldType::SingleSelect, &single_select_field_meta) + .unwrap() + .parse::() + .unwrap() + .select_options, + vec![done_option], ); // Multiple select @@ -104,24 +113,29 @@ mod tests { let ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); let cell_data_changeset = SelectOptionCellContentChangeset::from_insert(&ids).to_str(); let multi_select = MultiSelectTypeOptionBuilder::default() - .option(google_option) - .option(facebook_option); + .option(google_option.clone()) + .option(facebook_option.clone()); let multi_select_field_meta = FieldBuilder::new(multi_select).build(); let multi_type_option = MultiSelectTypeOption::from(&multi_select_field_meta); let cell_data = multi_type_option.apply_changeset(cell_data_changeset, None).unwrap(); assert_eq!( type_option - .decode_cell_data(cell_data, &multi_select_field_meta) - .content, - "Google,Facebook".to_owned() + .decode_cell_data(cell_data, &FieldType::MultiSelect, &multi_select_field_meta) + .unwrap() + .parse::() + .unwrap() + .select_options, + vec![google_option, facebook_option] ); //Number let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); let number_field_meta = FieldBuilder::new(number).build(); - let data = TypeOptionCellData::new("18443", FieldType::Number).json(); assert_eq!( - type_option.decode_cell_data(data, &number_field_meta).content, + type_option + .decode_cell_data("18443".to_owned(), &FieldType::Number, &number_field_meta) + .unwrap() + .to_string(), "$18,443".to_owned() ); } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs new file mode 100644 index 0000000000..ecb2a8e16f --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs @@ -0,0 +1,189 @@ +use crate::impl_type_option; +use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; +use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, EncodedCellData}; +use bytes::Bytes; +use fancy_regex::Regex; +use flowy_derive::ProtoBuf; +use flowy_error::{internal_error, FlowyError, FlowyResult}; +use flowy_grid_data_model::entities::{ + CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, +}; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +#[derive(Default)] +pub struct URLTypeOptionBuilder(URLTypeOption); +impl_into_box_type_option_builder!(URLTypeOptionBuilder); +impl_builder_from_json_str_and_from_bytes!(URLTypeOptionBuilder, URLTypeOption); + +impl TypeOptionBuilder for URLTypeOptionBuilder { + fn field_type(&self) -> FieldType { + self.0.field_type() + } + + fn entry(&self) -> &dyn TypeOptionDataEntry { + &self.0 + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] +pub struct URLTypeOption { + #[pb(index = 1)] + data: String, //It's not used yet. +} +impl_type_option!(URLTypeOption, FieldType::URL); + +impl CellDataOperation> for URLTypeOption { + fn decode_cell_data( + &self, + encoded_data: T, + decoded_field_type: &FieldType, + _field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into>, + { + if !decoded_field_type.is_url() { + return Ok(DecodedCellData::default()); + } + let cell_data = encoded_data.into().try_into_inner()?; + DecodedCellData::try_from_bytes(cell_data) + } + + fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result + where + C: Into, + { + let changeset = changeset.into(); + let mut url = "".to_string(); + if let Ok(Some(m)) = URL_REGEX.find(&changeset) { + 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) + } + } + Err(_) => { + format!("https://{}", s) + } + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] +pub struct URLCellData { + #[pb(index = 1)] + pub url: String, + + #[pb(index = 2)] + pub content: String, +} + +impl URLCellData { + pub fn new(s: &str) -> Self { + Self { + url: "".to_string(), + content: s.to_string(), + } + } + + fn to_json(&self) -> FlowyResult { + serde_json::to_string(self).map_err(internal_error) + } +} + +impl FromStr for URLCellData { + type Err = FlowyError; + + fn from_str(s: &str) -> Result { + serde_json::from_str::(s).map_err(internal_error) + } +} + +lazy_static! { + static ref URL_REGEX: Regex = Regex::new( + "[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)" + ) + .unwrap(); +} + +#[cfg(test)] +mod tests { + use crate::services::field::FieldBuilder; + use crate::services::field::{URLCellData, URLTypeOption}; + use crate::services::row::{CellDataOperation, EncodedCellData}; + use flowy_grid_data_model::entities::{FieldMeta, FieldType}; + + #[test] + fn url_type_option_test_no_url() { + let type_option = URLTypeOption::default(); + let field_type = FieldType::URL; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); + assert_changeset(&type_option, "123", &field_type, &field_meta, "123", ""); + } + + #[test] + fn url_type_option_test_contains_url() { + let type_option = URLTypeOption::default(); + let field_type = FieldType::URL; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); + assert_changeset( + &type_option, + "AppFlowy website - https://www.appflowy.io", + &field_type, + &field_meta, + "AppFlowy website - https://www.appflowy.io", + "https://www.appflowy.io/", + ); + + assert_changeset( + &type_option, + "AppFlowy website appflowy.io", + &field_type, + &field_meta, + "AppFlowy website appflowy.io", + "https://appflowy.io", + ); + } + + fn assert_changeset( + type_option: &URLTypeOption, + cell_data: &str, + field_type: &FieldType, + field_meta: &FieldMeta, + expected: &str, + expected_url: &str, + ) { + let encoded_data = type_option.apply_changeset(cell_data, None).unwrap(); + let decode_cell_data = decode_cell_data(encoded_data, type_option, field_meta, field_type); + assert_eq!(expected.to_owned(), decode_cell_data.content); + assert_eq!(expected_url.to_owned(), decode_cell_data.url); + } + + fn decode_cell_data>>( + encoded_data: T, + type_option: &URLTypeOption, + field_meta: &FieldMeta, + field_type: &FieldType, + ) -> URLCellData { + type_option + .decode_cell_data(encoded_data, field_type, field_meta) + .unwrap() + .parse::() + .unwrap() + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index b0695af1fd..82db6aac1b 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -1,9 +1,9 @@ use crate::dart_notification::{send_dart_notification, GridNotification}; +use crate::entities::CellIdentifier; use crate::manager::GridUser; -use crate::services::block_meta_manager::GridBlockMetaEditorManager; -use crate::services::entities::CellIdentifier; +use crate::services::block_meta_manager::GridBlockManager; use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder}; -use crate::services::persistence::block_index::BlockIndexPersistence; +use crate::services::persistence::block_index::BlockIndexCache; use crate::services::row::*; use bytes::Bytes; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; @@ -19,20 +19,26 @@ use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; -pub struct ClientGridEditor { +pub struct GridMetaEditor { grid_id: String, user: Arc, grid_pad: Arc>, rev_manager: Arc, - block_meta_manager: Arc, + block_manager: Arc, } -impl ClientGridEditor { +impl Drop for GridMetaEditor { + fn drop(&mut self) { + tracing::trace!("Drop GridMetaEditor"); + } +} + +impl GridMetaEditor { pub async fn new( grid_id: &str, user: Arc, mut rev_manager: RevisionManager, - persistence: Arc, + persistence: Arc, ) -> FlowyResult> { let token = user.token()?; let cloud = Arc::new(GridRevisionCloudService { token }); @@ -41,13 +47,13 @@ impl ClientGridEditor { let grid_pad = Arc::new(RwLock::new(grid_pad)); let blocks = grid_pad.read().await.get_block_metas(); - let block_meta_manager = Arc::new(GridBlockMetaEditorManager::new(grid_id, &user, blocks, persistence).await?); + let block_meta_manager = Arc::new(GridBlockManager::new(grid_id, &user, blocks, persistence).await?); Ok(Arc::new(Self { grid_id: grid_id.to_owned(), user, grid_pad, rev_manager, - block_meta_manager, + block_manager: block_meta_manager, })) } @@ -121,12 +127,22 @@ impl ClientGridEditor { Ok(()) } - pub async fn create_next_field_meta(&self, field_type: &FieldType) -> FlowyResult { + pub async fn next_field_meta(&self, field_type: &FieldType) -> FlowyResult { let name = format!("Property {}", self.grid_pad.read().await.fields().len() + 1); let field_meta = FieldBuilder::from_field_type(field_type).name(&name).build(); Ok(field_meta) } + pub async fn create_next_field_meta(&self, field_type: &FieldType) -> FlowyResult { + let field_meta = self.next_field_meta(field_type).await?; + let _ = self + .modify(|grid| Ok(grid.create_field_meta(field_meta.clone(), None)?)) + .await?; + let _ = self.notify_did_insert_grid_field(&field_meta.id).await?; + + Ok(field_meta) + } + pub async fn contain_field(&self, field_id: &str) -> bool { self.grid_pad.read().await.contain_field(field_id) } @@ -244,10 +260,7 @@ impl ClientGridEditor { let row_order = RowOrder::from(&row_meta); // insert the row - let row_count = self - .block_meta_manager - .create_row(&block_id, row_meta, start_row_id) - .await?; + let row_count = self.block_manager.create_row(&block_id, row_meta, start_row_id).await?; // update block row count let changeset = GridBlockMetaChangeset::from_row_count(&block_id, row_count); @@ -267,7 +280,7 @@ impl ClientGridEditor { .or_insert_with(Vec::new) .push(row_meta); } - let changesets = self.block_meta_manager.insert_row(rows_by_block_id).await?; + let changesets = self.block_manager.insert_row(rows_by_block_id).await?; for changeset in changesets { let _ = self.update_block(changeset).await?; } @@ -276,7 +289,7 @@ impl ClientGridEditor { pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> { let field_metas = self.get_field_metas::(None).await?; - self.block_meta_manager + self.block_manager .update_row(changeset, |row_meta| make_row_from_row_meta(&field_metas, row_meta)) .await } @@ -299,7 +312,7 @@ impl ClientGridEditor { } pub async fn get_row(&self, row_id: &str) -> FlowyResult> { - match self.block_meta_manager.get_row_meta(row_id).await? { + match self.block_manager.get_row_meta(row_id).await? { None => Ok(None), Some(row_meta) => { let field_metas = self.get_field_metas::(None).await?; @@ -311,7 +324,7 @@ impl ClientGridEditor { } } pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> { - let _ = self.block_meta_manager.delete_row(row_id).await?; + let _ = self.block_manager.delete_row(row_id).await?; Ok(()) } @@ -321,12 +334,12 @@ impl ClientGridEditor { pub async fn get_cell(&self, params: &CellIdentifier) -> Option { let field_meta = self.get_field_meta(¶ms.field_id).await?; - let row_meta = self.block_meta_manager.get_row_meta(¶ms.row_id).await.ok()??; + let row_meta = self.block_manager.get_row_meta(¶ms.row_id).await.ok()??; make_cell(¶ms.field_id, &field_meta, &row_meta) } pub async fn get_cell_meta(&self, row_id: &str, field_id: &str) -> FlowyResult> { - let row_meta = self.block_meta_manager.get_row_meta(row_id).await?; + let row_meta = self.block_manager.get_row_meta(row_id).await?; match row_meta { None => Ok(None), Some(row_meta) => { @@ -372,7 +385,7 @@ impl ClientGridEditor { cell_content_changeset, }; let _ = self - .block_meta_manager + .block_manager .update_cell(cell_changeset, |row_meta| { make_row_from_row_meta(&field_metas, row_meta) }) @@ -393,7 +406,7 @@ impl ClientGridEditor { } pub async fn delete_rows(&self, row_orders: Vec) -> FlowyResult<()> { - let changesets = self.block_meta_manager.delete_rows(row_orders).await?; + let changesets = self.block_manager.delete_rows(row_orders).await?; for changeset in changesets { let _ = self.update_block(changeset).await?; } @@ -405,7 +418,7 @@ impl ClientGridEditor { let field_orders = pad_read_guard.get_field_orders(); let mut block_orders = vec![]; for block_order in pad_read_guard.get_block_metas() { - let row_orders = self.block_meta_manager.get_row_orders(&block_order.block_id).await?; + let row_orders = self.block_manager.get_row_orders(&block_order.block_id).await?; let block_order = GridBlockOrder { block_id: block_order.block_id, row_orders, @@ -432,7 +445,7 @@ impl ClientGridEditor { .collect::>(), Some(block_ids) => block_ids, }; - let snapshots = self.block_meta_manager.make_block_snapshots(block_ids).await?; + let snapshots = self.block_manager.make_block_snapshots(block_ids).await?; Ok(snapshots) } @@ -466,10 +479,7 @@ impl ClientGridEditor { } pub async fn move_row(&self, row_id: &str, from: i32, to: i32) -> FlowyResult<()> { - let _ = self - .block_meta_manager - .move_row(row_id, from as usize, to as usize) - .await?; + let _ = self.block_manager.move_row(row_id, from as usize, to as usize).await?; Ok(()) } @@ -477,6 +487,35 @@ impl ClientGridEditor { 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>, @@ -555,7 +594,7 @@ impl ClientGridEditor { } #[cfg(feature = "flowy_unit_test")] -impl ClientGridEditor { +impl GridMetaEditor { pub fn rev_manager(&self) -> Arc { self.rev_manager.clone() } diff --git a/frontend/rust-lib/flowy-grid/src/services/mod.rs b/frontend/rust-lib/flowy-grid/src/services/mod.rs index 036efd6ada..c9a8217bd8 100644 --- a/frontend/rust-lib/flowy-grid/src/services/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/mod.rs @@ -2,7 +2,6 @@ mod util; pub mod block_meta_editor; mod block_meta_manager; -pub mod entities; pub mod field; pub mod grid_editor; pub mod persistence; diff --git a/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs b/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs index df75ec629c..c62dc502ad 100644 --- a/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs +++ b/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs @@ -7,11 +7,11 @@ use flowy_database::{ use flowy_error::FlowyResult; use std::sync::Arc; -pub struct BlockIndexPersistence { +pub struct BlockIndexCache { database: Arc, } -impl BlockIndexPersistence { +impl BlockIndexCache { pub fn new(database: Arc) -> Self { Self { database } } @@ -26,7 +26,7 @@ impl BlockIndexPersistence { Ok(block_id) } - pub fn insert_or_update(&self, block_id: &str, row_id: &str) -> FlowyResult<()> { + pub fn insert(&self, block_id: &str, row_id: &str) -> FlowyResult<()> { let conn = self.database.db_connection()?; let item = IndexItem { row_id: row_id.to_string(), diff --git a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs index 5f6dbc74fd..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 @@ -1,20 +1,31 @@ use crate::services::field::*; -use flowy_error::FlowyError; +use bytes::Bytes; +use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{CellMeta, FieldMeta, FieldType}; use serde::{Deserialize, Serialize}; use std::fmt::Formatter; +use std::str::FromStr; -pub trait CellDataOperation { - fn decode_cell_data(&self, data: String, field_meta: &FieldMeta) -> DecodedCellData; - fn apply_changeset>( +pub trait CellDataOperation { + fn decode_cell_data( &self, - changeset: T, + encoded_data: T, + decoded_field_type: &FieldType, + field_meta: &FieldMeta, + ) -> FlowyResult + where + T: Into; + + // + fn apply_changeset>( + &self, + changeset: C, cell_meta: Option, - ) -> Result; + ) -> FlowyResult; } #[derive(Debug)] -pub struct CellContentChangeset(String); +pub struct CellContentChangeset(pub String); impl std::fmt::Display for CellContentChangeset { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -43,6 +54,12 @@ pub struct TypeOptionCellData { pub field_type: FieldType, } +impl TypeOptionCellData { + pub fn split(self) -> (String, FieldType) { + (self.data, self.field_type) + } +} + impl std::str::FromStr for TypeOptionCellData { type Err = FlowyError; @@ -52,6 +69,14 @@ impl std::str::FromStr for TypeOptionCellData { } } +impl std::convert::TryInto for String { + type Error = FlowyError; + + fn try_into(self) -> Result { + TypeOptionCellData::from_str(&self) + } +} + impl TypeOptionCellData { pub fn new(data: T, field_type: FieldType) -> Self { TypeOptionCellData { @@ -87,6 +112,10 @@ impl TypeOptionCellData { pub fn is_multi_select(&self) -> bool { self.field_type == FieldType::MultiSelect } + + pub fn is_select_option(&self) -> bool { + self.field_type == FieldType::MultiSelect || self.field_type == FieldType::SingleSelect + } } /// The changeset will be deserialized into specific data base on the FieldType. @@ -96,63 +125,145 @@ pub fn apply_cell_data_changeset>( cell_meta: Option, field_meta: &FieldMeta, ) -> Result { - match field_meta.field_type { + let s = match field_meta.field_type { FieldType::RichText => RichTextTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), FieldType::Number => NumberTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), FieldType::DateTime => DateTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), FieldType::SingleSelect => SingleSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), FieldType::MultiSelect => MultiSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), FieldType::Checkbox => CheckboxTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), + FieldType::URL => URLTypeOption::from(field_meta).apply_changeset(changeset, cell_meta), + }?; + + Ok(TypeOptionCellData::new(s, field_meta.field_type.clone()).json()) +} + +pub fn decode_cell_data_from_type_option_cell_data>( + data: T, + field_meta: &FieldMeta, + field_type: &FieldType, +) -> DecodedCellData { + if let Ok(type_option_cell_data) = data.try_into() { + let (encoded_data, s_field_type) = type_option_cell_data.split(); + match decode_cell_data(encoded_data, &s_field_type, field_type, field_meta) { + Ok(cell_data) => cell_data, + Err(e) => { + tracing::error!("Decode cell data failed, {:?}", e); + DecodedCellData::default() + } + } + } else { + tracing::error!("Decode type option data failed"); + DecodedCellData::default() } } -pub fn decode_cell_data(data: String, field_meta: &FieldMeta, field_type: &FieldType) -> Option { - let s = match field_type { - FieldType::RichText => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - FieldType::Number => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - FieldType::DateTime => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - FieldType::SingleSelect => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - FieldType::MultiSelect => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), - FieldType::Checkbox => field_meta - .get_type_option_entry::(field_type)? - .decode_cell_data(data, field_meta), +pub fn decode_cell_data>( + encoded_data: T, + s_field_type: &FieldType, + t_field_type: &FieldType, + field_meta: &FieldMeta, +) -> FlowyResult { + let encoded_data = encoded_data.into(); + let get_cell_data = || { + let data = match t_field_type { + FieldType::RichText => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::Number => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::DateTime => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::SingleSelect => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::MultiSelect => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::Checkbox => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + FieldType::URL => field_meta + .get_type_option_entry::(t_field_type)? + .decode_cell_data(encoded_data, s_field_type, field_meta), + }; + Some(data) }; - tracing::Span::current().record( - "content", - &format!("{:?}: {}", field_meta.field_type, s.content).as_str(), - ); - Some(s) + + match get_cell_data() { + Some(Ok(data)) => Ok(data), + Some(Err(err)) => { + tracing::error!("{:?}", err); + Ok(DecodedCellData::default()) + } + None => Ok(DecodedCellData::default()), + } +} + +pub(crate) struct EncodedCellData(pub Option); + +impl EncodedCellData { + pub fn try_into_inner(self) -> FlowyResult { + match self.0 { + None => Err(ErrorCode::InvalidData.into()), + Some(data) => Ok(data), + } + } +} + +impl std::convert::From for EncodedCellData +where + T: FromStr, +{ + fn from(s: String) -> Self { + match T::from_str(&s) { + Ok(inner) => EncodedCellData(Some(inner)), + Err(e) => { + tracing::error!("Deserialize Cell Data failed: {}", e); + EncodedCellData(None) + } + } + } } #[derive(Default)] pub struct DecodedCellData { - pub raw: String, - pub content: String, + pub data: Vec, } impl DecodedCellData { - pub fn from_content(content: String) -> Self { + pub fn new>(data: T) -> Self { Self { - raw: content.clone(), - content, + data: data.as_ref().to_vec(), } } - pub fn new(raw: String, content: String) -> Self { - Self { raw, content } + pub fn try_from_bytes>(bytes: T) -> FlowyResult + where + >::Error: std::fmt::Debug, + { + let bytes = bytes.try_into().map_err(internal_error)?; + Ok(Self { data: bytes.to_vec() }) } - pub fn split(self) -> (String, String) { - (self.raw, self.content) + pub fn parse<'a, T: TryFrom<&'a [u8]>>(&'a self) -> FlowyResult + where + >::Error: std::fmt::Debug, + { + T::try_from(self.data.as_ref()).map_err(internal_error) + } +} + +impl ToString for DecodedCellData { + fn to_string(&self) -> String { + match String::from_utf8(self.data.clone()) { + Ok(s) => s, + Err(e) => { + tracing::error!("DecodedCellData to string failed: {:?}", e); + "".to_string() + } + } } } diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs index edcb102595..865058ffb6 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs @@ -1,4 +1,4 @@ -use crate::services::row::decode_cell_data; +use crate::services::row::decode_cell_data_from_type_option_cell_data; use flowy_error::FlowyResult; use flowy_grid_data_model::entities::{ Cell, CellMeta, FieldMeta, GridBlock, GridBlockOrder, RepeatedGridBlock, Row, RowMeta, RowOrder, @@ -31,15 +31,15 @@ pub fn make_cell_by_field_id( cell_meta: CellMeta, ) -> Option<(String, Cell)> { let field_meta = field_map.get(&field_id)?; - let (raw, content) = decode_cell_data(cell_meta.data, field_meta, &field_meta.field_type)?.split(); - let cell = Cell::new(&field_id, content, raw); + let data = decode_cell_data_from_type_option_cell_data(cell_meta.data, field_meta, &field_meta.field_type).data; + let cell = Cell::new(&field_id, data); Some((field_id, cell)) } pub fn make_cell(field_id: &str, field_meta: &FieldMeta, row_meta: &RowMeta) -> Option { let cell_meta = row_meta.cells.get(field_id)?.clone(); - let (raw, content) = decode_cell_data(cell_meta.data, field_meta, &field_meta.field_type)?.split(); - Some(Cell::new(field_id, content, raw)) + let data = decode_cell_data_from_type_option_cell_data(cell_meta.data, field_meta, &field_meta.field_type).data; + Some(Cell::new(field_id, data)) } pub(crate) fn make_row_orders_from_row_metas(row_metas: &[Arc]) -> Vec { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs index a954694a16..7b210d4f95 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs @@ -2,10 +2,10 @@ use crate::grid::script::EditorScript::*; use crate::grid::script::*; use chrono::NaiveDateTime; use flowy_grid::services::field::{ - DateCellContentChangeset, MultiSelectTypeOption, SelectOption, SelectOptionCellContentChangeset, + DateCellContentChangeset, DateCellData, MultiSelectTypeOption, SelectOption, SelectOptionCellContentChangeset, SingleSelectTypeOption, SELECTION_IDS_SEPARATOR, }; -use flowy_grid::services::row::{decode_cell_data, CreateRowMetaBuilder}; +use flowy_grid::services::row::{decode_cell_data_from_type_option_cell_data, CreateRowMetaBuilder}; use flowy_grid_data_model::entities::{ CellChangeset, FieldChangesetParams, FieldType, GridBlockMeta, GridBlockMetaChangeset, RowMetaChangeset, TypeOptionDataEntry, @@ -262,6 +262,9 @@ async fn grid_row_add_cells_test() { FieldType::Checkbox => { builder.add_cell(&field.id, "false".to_string()).unwrap(); } + FieldType::URL => { + builder.add_cell(&field.id, "1".to_string()).unwrap(); + } } } let context = builder.build(); @@ -291,10 +294,10 @@ async fn grid_row_add_date_cell_test() { let date_field = date_field.unwrap(); let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone(); assert_eq!( - decode_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type) + decode_cell_data_from_type_option_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type) + .parse::() .unwrap() - .split() - .1, + .date, "2022/03/16", ); let scripts = vec![CreateRow { context }]; @@ -328,6 +331,7 @@ async fn grid_cell_update() { SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str() } FieldType::Checkbox => "1".to_string(), + FieldType::URL => "1".to_string(), }; scripts.push(UpdateCell { @@ -349,6 +353,7 @@ async fn grid_cell_update() { FieldType::SingleSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false), FieldType::MultiSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false), FieldType::Checkbox => ("2".to_string(), false), + FieldType::URL => ("2".to_string(), false), }; scripts.push(UpdateCell { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/script.rs index 611ee937e2..37c3b736d4 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/script.rs @@ -1,6 +1,6 @@ use bytes::Bytes; use flowy_grid::services::field::*; -use flowy_grid::services::grid_editor::{ClientGridEditor, GridPadBuilder}; +use flowy_grid::services::grid_editor::{GridMetaEditor, GridPadBuilder}; use flowy_grid::services::row::CreateRowMetaPayload; use flowy_grid_data_model::entities::{ BuildGridContext, CellChangeset, Field, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, GridBlockMeta, @@ -72,7 +72,7 @@ pub enum EditorScript { pub struct GridEditorTest { pub sdk: FlowySDKTest, pub grid_id: String, - pub editor: Arc, + pub editor: Arc, pub field_metas: Vec, pub grid_blocks: Vec, pub row_metas: Vec>, @@ -239,7 +239,7 @@ impl GridEditorTest { } } -async fn get_row_metas(editor: &Arc) -> Vec> { +async fn get_row_metas(editor: &Arc) -> Vec> { editor .grid_block_snapshots(None) .await @@ -354,6 +354,10 @@ fn make_template_1_grid() -> BuildGridContext { let checkbox = CheckboxTypeOptionBuilder::default(); let checkbox_field = FieldBuilder::new(checkbox).name("is done").visibility(true).build(); + // URL + let url = URLTypeOptionBuilder::default(); + let url_field = FieldBuilder::new(url).name("link").visibility(true).build(); + GridBuilder::default() .add_field(text_field) .add_field(single_select_field) @@ -361,6 +365,7 @@ fn make_template_1_grid() -> BuildGridContext { .add_field(number_field) .add_field(date_field) .add_field(checkbox_field) + .add_field(url_field) .add_empty_row() .add_empty_row() .add_empty_row() diff --git a/frontend/rust-lib/flowy-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/editor.rs b/frontend/rust-lib/flowy-text-block/src/editor.rs index d43fc24f1c..c1e50536a5 100644 --- a/frontend/rust-lib/flowy-text-block/src/editor.rs +++ b/frontend/rust-lib/flowy-text-block/src/editor.rs @@ -21,7 +21,7 @@ use lib_ws::WSConnectState; use std::sync::Arc; use tokio::sync::{mpsc, oneshot}; -pub struct ClientTextBlockEditor { +pub struct TextBlockEditor { pub doc_id: String, #[allow(dead_code)] rev_manager: Arc, @@ -30,7 +30,7 @@ pub struct ClientTextBlockEditor { edit_cmd_tx: EditorCommandSender, } -impl ClientTextBlockEditor { +impl TextBlockEditor { #[allow(unused_variables)] pub(crate) async fn new( doc_id: &str, @@ -185,7 +185,7 @@ impl ClientTextBlockEditor { pub(crate) fn receive_ws_state(&self, _state: &WSConnectState) {} } -impl std::ops::Drop for ClientTextBlockEditor { +impl std::ops::Drop for TextBlockEditor { fn drop(&mut self) { tracing::trace!("{} ClientBlockEditor was dropped", self.doc_id) } @@ -204,7 +204,7 @@ fn spawn_edit_queue( } #[cfg(feature = "flowy_unit_test")] -impl ClientTextBlockEditor { +impl TextBlockEditor { pub async fn text_block_delta(&self) -> FlowyResult { let (ret, rx) = oneshot::channel::>(); let msg = EditorCommand::ReadDelta { ret }; diff --git a/frontend/rust-lib/flowy-text-block/src/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/manager.rs b/frontend/rust-lib/flowy-text-block/src/manager.rs index 8a34c6916d..9325fe6001 100644 --- a/frontend/rust-lib/flowy-text-block/src/manager.rs +++ b/frontend/rust-lib/flowy-text-block/src/manager.rs @@ -1,4 +1,4 @@ -use crate::{editor::ClientTextBlockEditor, errors::FlowyError, BlockCloudService}; +use crate::{editor::TextBlockEditor, errors::FlowyError, BlockCloudService}; use bytes::Bytes; use dashmap::DashMap; use flowy_database::ConnectionPool; @@ -47,8 +47,8 @@ impl TextBlockManager { Ok(()) } - #[tracing::instrument(level = "debug", skip(self, block_id), fields(block_id), err)] - pub async fn open_block>(&self, block_id: T) -> Result, FlowyError> { + #[tracing::instrument(level = "trace", skip(self, block_id), fields(block_id), err)] + pub async fn open_block>(&self, block_id: T) -> Result, FlowyError> { let block_id = block_id.as_ref(); tracing::Span::current().record("block_id", &block_id); self.get_block_editor(block_id).await @@ -108,7 +108,7 @@ impl TextBlockManager { } impl TextBlockManager { - async fn get_block_editor(&self, block_id: &str) -> FlowyResult> { + async fn get_block_editor(&self, block_id: &str) -> FlowyResult> { match self.editor_map.get(block_id) { None => { let db_pool = self.user.db_pool()?; @@ -123,7 +123,7 @@ impl TextBlockManager { &self, block_id: &str, pool: Arc, - ) -> Result, FlowyError> { + ) -> Result, FlowyError> { let user = self.user.clone(); let token = self.user.token()?; let rev_manager = self.make_rev_manager(block_id, pool.clone())?; @@ -132,7 +132,7 @@ impl TextBlockManager { server: self.cloud_service.clone(), }); let doc_editor = - ClientTextBlockEditor::new(block_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service).await?; + TextBlockEditor::new(block_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service).await?; self.editor_map.insert(block_id, &doc_editor); Ok(doc_editor) } @@ -180,7 +180,7 @@ impl RevisionCloudService for TextBlockRevisionCloudService { } pub struct TextBlockEditorMap { - inner: DashMap>, + inner: DashMap>, } impl TextBlockEditorMap { @@ -188,14 +188,14 @@ impl TextBlockEditorMap { Self { inner: DashMap::new() } } - pub(crate) fn insert(&self, block_id: &str, doc: &Arc) { + pub(crate) fn insert(&self, block_id: &str, doc: &Arc) { if self.inner.contains_key(block_id) { log::warn!("Doc:{} already exists in cache", block_id); } self.inner.insert(block_id.to_string(), doc.clone()); } - pub(crate) fn get(&self, block_id: &str) -> Option> { + pub(crate) fn get(&self, block_id: &str) -> Option> { Some(self.inner.get(block_id)?.clone()) } diff --git a/frontend/rust-lib/flowy-text-block/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/rust-lib/flowy-text-block/tests/document/script.rs b/frontend/rust-lib/flowy-text-block/tests/document/script.rs index 5511896fc2..1722724c3f 100644 --- a/frontend/rust-lib/flowy-text-block/tests/document/script.rs +++ b/frontend/rust-lib/flowy-text-block/tests/document/script.rs @@ -1,6 +1,6 @@ use flowy_revision::disk::RevisionState; use flowy_test::{helper::ViewTest, FlowySDKTest}; -use flowy_text_block::editor::ClientTextBlockEditor; +use flowy_text_block::editor::TextBlockEditor; use flowy_text_block::TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS; use lib_ot::{core::Interval, rich_text::RichTextDelta}; use std::sync::Arc; @@ -19,7 +19,7 @@ pub enum EditorScript { pub struct TextBlockEditorTest { pub sdk: FlowySDKTest, - pub editor: Arc, + pub editor: Arc, } impl TextBlockEditorTest { diff --git a/frontend/scripts/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/frontend/scripts/makefile/tests.toml b/frontend/scripts/makefile/tests.toml index 89e3ea9140..b8082f651b 100644 --- a/frontend/scripts/makefile/tests.toml +++ b/frontend/scripts/makefile/tests.toml @@ -1,30 +1,18 @@ -[tasks.test_local] -category = "Build" -dependencies = ["rm_cache"] -description = "Build desktop targets." +[tasks.rust_unit_test] +run_task = { name = ["rust_lib_unit_test", "shared_lib_unit_test"] } + +[tasks.rust_lib_unit_test] +description = "Run rust-lib unit tests" script = ''' cd rust-lib -cargo test +RUST_LOG=info cargo test --no-default-features --features="sync" ''' +[tasks.shared_lib_unit_test] +description = "Run shared-lib unit test" +script = ''' +cd ../shared-lib +RUST_LOG=info cargo test --no-default-features +''' -[tasks.test_remote] -dependencies = ["rm_cache"] -script = """ -cd rust-lib -cargo test --features "flowy-folder/http_server","flowy-user/http_server" -""" - - -[tasks.run_server] -script = """ -cd backend -cargo run -""" - - -[tasks.rm_cache] -script = """ -rm -rf rust-lib/flowy-test/temp -""" \ No newline at end of file diff --git a/frontend/scripts/makefile/tool.toml b/frontend/scripts/makefile/tool.toml new file mode 100644 index 0000000000..bd0504f6fc --- /dev/null +++ b/frontend/scripts/makefile/tool.toml @@ -0,0 +1,27 @@ +[tasks.rust_clean] +script = [ + """ + cd rust-lib + cargo clean + + cd ../../shared-lib + cargo clean + + rm -rf lib-infra/.cache + """, +] +script_runner = "@shell" + +[tasks.rust_clean.windows] +script = [ + """ + cd rust-lib + cargo clean + + cd ../../shared-lib + cargo clean + + rmdir /s/q "lib-infra/.cache" + """, +] +script_runner = "@duckscript" \ No newline at end of file diff --git a/shared-lib/flowy-derive/src/proto_buf/deserialize.rs b/shared-lib/flowy-derive/src/proto_buf/deserialize.rs index e09f7ea847..f0325db3b0 100644 --- a/shared-lib/flowy-derive/src/proto_buf/deserialize.rs +++ b/shared-lib/flowy-derive/src/proto_buf/deserialize.rs @@ -30,6 +30,14 @@ pub fn make_de_token_steam(ctxt: &Ctxt, ast: &ASTContainer) -> Option for #struct_ident { + type Error = ::protobuf::ProtobufError; + fn try_from(bytes: &[u8]) -> Result { + let pb: crate::protobuf::#pb_ty = ::protobuf::Message::parse_from_bytes(bytes)?; + #struct_ident::try_from(pb) + } + } + impl std::convert::TryFrom for #struct_ident { type Error = ::protobuf::ProtobufError; fn try_from(mut pb: crate::protobuf::#pb_ty) -> Result { diff --git a/shared-lib/flowy-grid-data-model/src/entities/grid.rs b/shared-lib/flowy-grid-data-model/src/entities/grid.rs index 0dae40658c..5889e1093b 100644 --- a/shared-lib/flowy-grid-data-model/src/entities/grid.rs +++ b/shared-lib/flowy-grid-data-model/src/entities/grid.rs @@ -167,16 +167,19 @@ pub struct EditFieldPayload { #[pb(index = 1)] pub grid_id: String, - #[pb(index = 2, one_of)] - pub field_id: Option, + #[pb(index = 2)] + pub field_id: String, #[pb(index = 3)] pub field_type: FieldType, + + #[pb(index = 4)] + pub create_if_not_exist: bool, } pub struct EditFieldParams { pub grid_id: String, - pub field_id: Option, + pub field_id: String, pub field_type: FieldType, } @@ -185,17 +188,35 @@ impl TryInto for EditFieldPayload { fn try_into(self) -> Result { let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?; - // let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; + let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; Ok(EditFieldParams { grid_id: grid_id.0, - field_id: self.field_id, + field_id: field_id.0, + field_type: self.field_type, + }) + } +} + +pub struct CreateFieldParams { + pub grid_id: String, + pub field_type: FieldType, +} + +impl TryInto for EditFieldPayload { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?; + + Ok(CreateFieldParams { + grid_id: grid_id.0, field_type: self.field_type, }) } } #[derive(Debug, Default, ProtoBuf)] -pub struct EditFieldContext { +pub struct FieldTypeOptionContext { #[pb(index = 1)] pub grid_id: String, @@ -209,9 +230,12 @@ pub struct EditFieldContext { #[derive(Debug, Default, ProtoBuf)] pub struct FieldTypeOptionData { #[pb(index = 1)] - pub field_id: String, + pub grid_id: String, #[pb(index = 2)] + pub field: Field, + + #[pb(index = 3)] pub type_option_data: Vec, } @@ -460,17 +484,13 @@ pub struct Cell { pub field_id: String, #[pb(index = 2)] - pub content: String, - - #[pb(index = 3)] - pub data: String, + pub data: Vec, } impl Cell { - pub fn new(field_id: &str, content: String, data: String) -> Self { + pub fn new(field_id: &str, data: Vec) -> Self { Self { field_id: field_id.to_owned(), - content, data, } } @@ -478,8 +498,7 @@ impl Cell { pub fn empty(field_id: &str) -> Self { Self { field_id: field_id.to_owned(), - content: "".to_string(), - data: "".to_string(), + data: vec![], } } } @@ -856,6 +875,7 @@ pub enum FieldType { SingleSelect = 3, MultiSelect = 4, Checkbox = 5, + URL = 6, } impl std::default::Default for FieldType { @@ -888,6 +908,38 @@ impl FieldType { _ => 150, } } + + pub fn is_number(&self) -> bool { + self == &FieldType::Number + } + + pub fn is_text(&self) -> bool { + self == &FieldType::RichText + } + + pub fn is_checkbox(&self) -> bool { + self == &FieldType::Checkbox + } + + pub fn is_date(&self) -> bool { + self == &FieldType::DateTime + } + + pub fn is_single_select(&self) -> bool { + self == &FieldType::SingleSelect + } + + pub fn is_multi_select(&self) -> bool { + self == &FieldType::MultiSelect + } + + pub fn is_url(&self) -> bool { + self == &FieldType::URL + } + + pub fn is_select_option(&self) -> bool { + self == &FieldType::MultiSelect || self == &FieldType::SingleSelect + } } #[derive(Debug, Clone, Default, ProtoBuf)] diff --git a/shared-lib/flowy-grid-data-model/src/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-grid-data-model/src/protobuf/model/grid.rs b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs index 614d0e8b21..c087cc5ea3 100644 --- a/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs +++ b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs @@ -1644,9 +1644,9 @@ impl ::protobuf::reflect::ProtobufValue for GetEditFieldContextPayload { pub struct EditFieldPayload { // message fields pub grid_id: ::std::string::String, + pub field_id: ::std::string::String, pub field_type: FieldType, - // message oneof groups - pub one_of_field_id: ::std::option::Option, + pub create_if_not_exist: bool, // special fields pub unknown_fields: ::protobuf::UnknownFields, pub cached_size: ::protobuf::CachedSize, @@ -1658,11 +1658,6 @@ impl<'a> ::std::default::Default for &'a EditFieldPayload { } } -#[derive(Clone,PartialEq,Debug)] -pub enum EditFieldPayload_oneof_one_of_field_id { - field_id(::std::string::String), -} - impl EditFieldPayload { pub fn new() -> EditFieldPayload { ::std::default::Default::default() @@ -1698,49 +1693,26 @@ impl EditFieldPayload { pub fn get_field_id(&self) -> &str { - match self.one_of_field_id { - ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(ref v)) => v, - _ => "", - } + &self.field_id } pub fn clear_field_id(&mut self) { - self.one_of_field_id = ::std::option::Option::None; - } - - pub fn has_field_id(&self) -> bool { - match self.one_of_field_id { - ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(..)) => true, - _ => false, - } + self.field_id.clear(); } // Param is passed by value, moved pub fn set_field_id(&mut self, v: ::std::string::String) { - self.one_of_field_id = ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(v)) + self.field_id = v; } // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. pub fn mut_field_id(&mut self) -> &mut ::std::string::String { - if let ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(_)) = self.one_of_field_id { - } else { - self.one_of_field_id = ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(::std::string::String::new())); - } - match self.one_of_field_id { - ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(ref mut v)) => v, - _ => panic!(), - } + &mut self.field_id } // Take field pub fn take_field_id(&mut self) -> ::std::string::String { - if self.has_field_id() { - match self.one_of_field_id.take() { - ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(v)) => v, - _ => panic!(), - } - } else { - ::std::string::String::new() - } + ::std::mem::replace(&mut self.field_id, ::std::string::String::new()) } // .FieldType field_type = 3; @@ -1757,6 +1729,21 @@ impl EditFieldPayload { pub fn set_field_type(&mut self, v: FieldType) { self.field_type = v; } + + // bool create_if_not_exist = 4; + + + pub fn get_create_if_not_exist(&self) -> bool { + self.create_if_not_exist + } + pub fn clear_create_if_not_exist(&mut self) { + self.create_if_not_exist = false; + } + + // Param is passed by value, moved + pub fn set_create_if_not_exist(&mut self, v: bool) { + self.create_if_not_exist = v; + } } impl ::protobuf::Message for EditFieldPayload { @@ -1772,14 +1759,18 @@ impl ::protobuf::Message for EditFieldPayload { ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.grid_id)?; }, 2 => { - if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited { - return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type)); - } - self.one_of_field_id = ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(is.read_string()?)); + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?; }, 3 => { ::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.field_type, 3, &mut self.unknown_fields)? }, + 4 => { + if wire_type != ::protobuf::wire_format::WireTypeVarint { + return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type)); + } + let tmp = is.read_bool()?; + self.create_if_not_exist = tmp; + }, _ => { ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; }, @@ -1795,15 +1786,14 @@ impl ::protobuf::Message for EditFieldPayload { if !self.grid_id.is_empty() { my_size += ::protobuf::rt::string_size(1, &self.grid_id); } + if !self.field_id.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.field_id); + } if self.field_type != FieldType::RichText { my_size += ::protobuf::rt::enum_size(3, self.field_type); } - if let ::std::option::Option::Some(ref v) = self.one_of_field_id { - match v { - &EditFieldPayload_oneof_one_of_field_id::field_id(ref v) => { - my_size += ::protobuf::rt::string_size(2, &v); - }, - }; + if self.create_if_not_exist != false { + my_size += 2; } my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); self.cached_size.set(my_size); @@ -1814,15 +1804,14 @@ impl ::protobuf::Message for EditFieldPayload { if !self.grid_id.is_empty() { os.write_string(1, &self.grid_id)?; } + if !self.field_id.is_empty() { + os.write_string(2, &self.field_id)?; + } if self.field_type != FieldType::RichText { os.write_enum(3, ::protobuf::ProtobufEnum::value(&self.field_type))?; } - if let ::std::option::Option::Some(ref v) = self.one_of_field_id { - match v { - &EditFieldPayload_oneof_one_of_field_id::field_id(ref v) => { - os.write_string(2, v)?; - }, - }; + if self.create_if_not_exist != false { + os.write_bool(4, self.create_if_not_exist)?; } os.write_unknown_fields(self.get_unknown_fields())?; ::std::result::Result::Ok(()) @@ -1867,16 +1856,21 @@ impl ::protobuf::Message for EditFieldPayload { |m: &EditFieldPayload| { &m.grid_id }, |m: &mut EditFieldPayload| { &mut m.grid_id }, )); - fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>( + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( "field_id", - EditFieldPayload::has_field_id, - EditFieldPayload::get_field_id, + |m: &EditFieldPayload| { &m.field_id }, + |m: &mut EditFieldPayload| { &mut m.field_id }, )); fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum>( "field_type", |m: &EditFieldPayload| { &m.field_type }, |m: &mut EditFieldPayload| { &mut m.field_type }, )); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>( + "create_if_not_exist", + |m: &EditFieldPayload| { &m.create_if_not_exist }, + |m: &mut EditFieldPayload| { &mut m.create_if_not_exist }, + )); ::protobuf::reflect::MessageDescriptor::new_pb_name::( "EditFieldPayload", fields, @@ -1894,8 +1888,9 @@ impl ::protobuf::Message for EditFieldPayload { impl ::protobuf::Clear for EditFieldPayload { fn clear(&mut self) { self.grid_id.clear(); - self.one_of_field_id = ::std::option::Option::None; + self.field_id.clear(); self.field_type = FieldType::RichText; + self.create_if_not_exist = false; self.unknown_fields.clear(); } } @@ -1913,7 +1908,7 @@ impl ::protobuf::reflect::ProtobufValue for EditFieldPayload { } #[derive(PartialEq,Clone,Default)] -pub struct EditFieldContext { +pub struct FieldTypeOptionContext { // message fields pub grid_id: ::std::string::String, pub grid_field: ::protobuf::SingularPtrField, @@ -1923,14 +1918,14 @@ pub struct EditFieldContext { pub cached_size: ::protobuf::CachedSize, } -impl<'a> ::std::default::Default for &'a EditFieldContext { - fn default() -> &'a EditFieldContext { - ::default_instance() +impl<'a> ::std::default::Default for &'a FieldTypeOptionContext { + fn default() -> &'a FieldTypeOptionContext { + ::default_instance() } } -impl EditFieldContext { - pub fn new() -> EditFieldContext { +impl FieldTypeOptionContext { + pub fn new() -> FieldTypeOptionContext { ::std::default::Default::default() } @@ -2020,7 +2015,7 @@ impl EditFieldContext { } } -impl ::protobuf::Message for EditFieldContext { +impl ::protobuf::Message for FieldTypeOptionContext { fn is_initialized(&self) -> bool { for v in &self.grid_field { if !v.is_initialized() { @@ -2112,8 +2107,8 @@ impl ::protobuf::Message for EditFieldContext { Self::descriptor_static() } - fn new() -> EditFieldContext { - EditFieldContext::new() + fn new() -> FieldTypeOptionContext { + FieldTypeOptionContext::new() } fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor { @@ -2122,34 +2117,34 @@ impl ::protobuf::Message for EditFieldContext { let mut fields = ::std::vec::Vec::new(); fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( "grid_id", - |m: &EditFieldContext| { &m.grid_id }, - |m: &mut EditFieldContext| { &mut m.grid_id }, + |m: &FieldTypeOptionContext| { &m.grid_id }, + |m: &mut FieldTypeOptionContext| { &mut m.grid_id }, )); fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage>( "grid_field", - |m: &EditFieldContext| { &m.grid_field }, - |m: &mut EditFieldContext| { &mut m.grid_field }, + |m: &FieldTypeOptionContext| { &m.grid_field }, + |m: &mut FieldTypeOptionContext| { &mut m.grid_field }, )); fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>( "type_option_data", - |m: &EditFieldContext| { &m.type_option_data }, - |m: &mut EditFieldContext| { &mut m.type_option_data }, + |m: &FieldTypeOptionContext| { &m.type_option_data }, + |m: &mut FieldTypeOptionContext| { &mut m.type_option_data }, )); - ::protobuf::reflect::MessageDescriptor::new_pb_name::( - "EditFieldContext", + ::protobuf::reflect::MessageDescriptor::new_pb_name::( + "FieldTypeOptionContext", fields, file_descriptor_proto() ) }) } - fn default_instance() -> &'static EditFieldContext { - static instance: ::protobuf::rt::LazyV2 = ::protobuf::rt::LazyV2::INIT; - instance.get(EditFieldContext::new) + fn default_instance() -> &'static FieldTypeOptionContext { + static instance: ::protobuf::rt::LazyV2 = ::protobuf::rt::LazyV2::INIT; + instance.get(FieldTypeOptionContext::new) } } -impl ::protobuf::Clear for EditFieldContext { +impl ::protobuf::Clear for FieldTypeOptionContext { fn clear(&mut self) { self.grid_id.clear(); self.grid_field.clear(); @@ -2158,13 +2153,13 @@ impl ::protobuf::Clear for EditFieldContext { } } -impl ::std::fmt::Debug for EditFieldContext { +impl ::std::fmt::Debug for FieldTypeOptionContext { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { ::protobuf::text_format::fmt(self, f) } } -impl ::protobuf::reflect::ProtobufValue for EditFieldContext { +impl ::protobuf::reflect::ProtobufValue for FieldTypeOptionContext { fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef { ::protobuf::reflect::ReflectValueRef::Message(self) } @@ -2173,7 +2168,8 @@ impl ::protobuf::reflect::ProtobufValue for EditFieldContext { #[derive(PartialEq,Clone,Default)] pub struct FieldTypeOptionData { // message fields - pub field_id: ::std::string::String, + pub grid_id: ::std::string::String, + pub field: ::protobuf::SingularPtrField, pub type_option_data: ::std::vec::Vec, // special fields pub unknown_fields: ::protobuf::UnknownFields, @@ -2191,33 +2187,66 @@ impl FieldTypeOptionData { ::std::default::Default::default() } - // string field_id = 1; + // string grid_id = 1; - pub fn get_field_id(&self) -> &str { - &self.field_id + pub fn get_grid_id(&self) -> &str { + &self.grid_id } - pub fn clear_field_id(&mut self) { - self.field_id.clear(); + pub fn clear_grid_id(&mut self) { + self.grid_id.clear(); } // Param is passed by value, moved - pub fn set_field_id(&mut self, v: ::std::string::String) { - self.field_id = v; + pub fn set_grid_id(&mut self, v: ::std::string::String) { + self.grid_id = v; } // Mutable pointer to the field. // If field is not initialized, it is initialized with default value first. - pub fn mut_field_id(&mut self) -> &mut ::std::string::String { - &mut self.field_id + pub fn mut_grid_id(&mut self) -> &mut ::std::string::String { + &mut self.grid_id } // Take field - pub fn take_field_id(&mut self) -> ::std::string::String { - ::std::mem::replace(&mut self.field_id, ::std::string::String::new()) + pub fn take_grid_id(&mut self) -> ::std::string::String { + ::std::mem::replace(&mut self.grid_id, ::std::string::String::new()) } - // bytes type_option_data = 2; + // .Field field = 2; + + + pub fn get_field(&self) -> &Field { + self.field.as_ref().unwrap_or_else(|| ::default_instance()) + } + pub fn clear_field(&mut self) { + self.field.clear(); + } + + pub fn has_field(&self) -> bool { + self.field.is_some() + } + + // Param is passed by value, moved + pub fn set_field(&mut self, v: Field) { + self.field = ::protobuf::SingularPtrField::some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_field(&mut self) -> &mut Field { + if self.field.is_none() { + self.field.set_default(); + } + self.field.as_mut().unwrap() + } + + // Take field + pub fn take_field(&mut self) -> Field { + self.field.take().unwrap_or_else(|| Field::new()) + } + + // bytes type_option_data = 3; pub fn get_type_option_data(&self) -> &[u8] { @@ -2246,6 +2275,11 @@ impl FieldTypeOptionData { impl ::protobuf::Message for FieldTypeOptionData { fn is_initialized(&self) -> bool { + for v in &self.field { + if !v.is_initialized() { + return false; + } + }; true } @@ -2254,9 +2288,12 @@ impl ::protobuf::Message for FieldTypeOptionData { let (field_number, wire_type) = is.read_tag_unpack()?; match field_number { 1 => { - ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?; + ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.grid_id)?; }, 2 => { + ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.field)?; + }, + 3 => { ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.type_option_data)?; }, _ => { @@ -2271,11 +2308,15 @@ impl ::protobuf::Message for FieldTypeOptionData { #[allow(unused_variables)] fn compute_size(&self) -> u32 { let mut my_size = 0; - if !self.field_id.is_empty() { - my_size += ::protobuf::rt::string_size(1, &self.field_id); + if !self.grid_id.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.grid_id); + } + if let Some(ref v) = self.field.as_ref() { + let len = v.compute_size(); + my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len; } if !self.type_option_data.is_empty() { - my_size += ::protobuf::rt::bytes_size(2, &self.type_option_data); + my_size += ::protobuf::rt::bytes_size(3, &self.type_option_data); } my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); self.cached_size.set(my_size); @@ -2283,11 +2324,16 @@ impl ::protobuf::Message for FieldTypeOptionData { } fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> { - if !self.field_id.is_empty() { - os.write_string(1, &self.field_id)?; + if !self.grid_id.is_empty() { + os.write_string(1, &self.grid_id)?; + } + if let Some(ref v) = self.field.as_ref() { + os.write_tag(2, ::protobuf::wire_format::WireTypeLengthDelimited)?; + os.write_raw_varint32(v.get_cached_size())?; + v.write_to_with_cached_sizes(os)?; } if !self.type_option_data.is_empty() { - os.write_bytes(2, &self.type_option_data)?; + os.write_bytes(3, &self.type_option_data)?; } os.write_unknown_fields(self.get_unknown_fields())?; ::std::result::Result::Ok(()) @@ -2328,9 +2374,14 @@ impl ::protobuf::Message for FieldTypeOptionData { descriptor.get(|| { let mut fields = ::std::vec::Vec::new(); fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( - "field_id", - |m: &FieldTypeOptionData| { &m.field_id }, - |m: &mut FieldTypeOptionData| { &mut m.field_id }, + "grid_id", + |m: &FieldTypeOptionData| { &m.grid_id }, + |m: &mut FieldTypeOptionData| { &mut m.grid_id }, + )); + fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage>( + "field", + |m: &FieldTypeOptionData| { &m.field }, + |m: &mut FieldTypeOptionData| { &mut m.field }, )); fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>( "type_option_data", @@ -2353,7 +2404,8 @@ impl ::protobuf::Message for FieldTypeOptionData { impl ::protobuf::Clear for FieldTypeOptionData { fn clear(&mut self) { - self.field_id.clear(); + self.grid_id.clear(); + self.field.clear(); self.type_option_data.clear(); self.unknown_fields.clear(); } @@ -4691,8 +4743,7 @@ impl ::protobuf::reflect::ProtobufValue for GridBlock { pub struct Cell { // message fields pub field_id: ::std::string::String, - pub content: ::std::string::String, - pub data: ::std::string::String, + pub data: ::std::vec::Vec, // special fields pub unknown_fields: ::protobuf::UnknownFields, pub cached_size: ::protobuf::CachedSize, @@ -4735,36 +4786,10 @@ impl Cell { ::std::mem::replace(&mut self.field_id, ::std::string::String::new()) } - // string content = 2; + // bytes data = 2; - pub fn get_content(&self) -> &str { - &self.content - } - pub fn clear_content(&mut self) { - self.content.clear(); - } - - // Param is passed by value, moved - pub fn set_content(&mut self, v: ::std::string::String) { - self.content = v; - } - - // Mutable pointer to the field. - // If field is not initialized, it is initialized with default value first. - pub fn mut_content(&mut self) -> &mut ::std::string::String { - &mut self.content - } - - // Take field - pub fn take_content(&mut self) -> ::std::string::String { - ::std::mem::replace(&mut self.content, ::std::string::String::new()) - } - - // string data = 3; - - - pub fn get_data(&self) -> &str { + pub fn get_data(&self) -> &[u8] { &self.data } pub fn clear_data(&mut self) { @@ -4772,19 +4797,19 @@ impl Cell { } // Param is passed by value, moved - pub fn set_data(&mut self, v: ::std::string::String) { + pub fn set_data(&mut self, v: ::std::vec::Vec) { self.data = v; } // Mutable pointer to the field. // If field is not initialized, it is initialized with default value first. - pub fn mut_data(&mut self) -> &mut ::std::string::String { + pub fn mut_data(&mut self) -> &mut ::std::vec::Vec { &mut self.data } // Take field - pub fn take_data(&mut self) -> ::std::string::String { - ::std::mem::replace(&mut self.data, ::std::string::String::new()) + pub fn take_data(&mut self) -> ::std::vec::Vec { + ::std::mem::replace(&mut self.data, ::std::vec::Vec::new()) } } @@ -4801,10 +4826,7 @@ impl ::protobuf::Message for Cell { ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?; }, 2 => { - ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.content)?; - }, - 3 => { - ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?; + ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.data)?; }, _ => { ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; @@ -4821,11 +4843,8 @@ impl ::protobuf::Message for Cell { if !self.field_id.is_empty() { my_size += ::protobuf::rt::string_size(1, &self.field_id); } - if !self.content.is_empty() { - my_size += ::protobuf::rt::string_size(2, &self.content); - } if !self.data.is_empty() { - my_size += ::protobuf::rt::string_size(3, &self.data); + my_size += ::protobuf::rt::bytes_size(2, &self.data); } my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); self.cached_size.set(my_size); @@ -4836,11 +4855,8 @@ impl ::protobuf::Message for Cell { if !self.field_id.is_empty() { os.write_string(1, &self.field_id)?; } - if !self.content.is_empty() { - os.write_string(2, &self.content)?; - } if !self.data.is_empty() { - os.write_string(3, &self.data)?; + os.write_bytes(2, &self.data)?; } os.write_unknown_fields(self.get_unknown_fields())?; ::std::result::Result::Ok(()) @@ -4885,12 +4901,7 @@ impl ::protobuf::Message for Cell { |m: &Cell| { &m.field_id }, |m: &mut Cell| { &mut m.field_id }, )); - fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( - "content", - |m: &Cell| { &m.content }, - |m: &mut Cell| { &mut m.content }, - )); - fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>( "data", |m: &Cell| { &m.data }, |m: &mut Cell| { &mut m.data }, @@ -4912,7 +4923,6 @@ impl ::protobuf::Message for Cell { impl ::protobuf::Clear for Cell { fn clear(&mut self) { self.field_id.clear(); - self.content.clear(); self.data.clear(); self.unknown_fields.clear(); } @@ -8179,6 +8189,7 @@ pub enum FieldType { SingleSelect = 3, MultiSelect = 4, Checkbox = 5, + URL = 6, } impl ::protobuf::ProtobufEnum for FieldType { @@ -8194,6 +8205,7 @@ impl ::protobuf::ProtobufEnum for FieldType { 3 => ::std::option::Option::Some(FieldType::SingleSelect), 4 => ::std::option::Option::Some(FieldType::MultiSelect), 5 => ::std::option::Option::Some(FieldType::Checkbox), + 6 => ::std::option::Option::Some(FieldType::URL), _ => ::std::option::Option::None } } @@ -8206,6 +8218,7 @@ impl ::protobuf::ProtobufEnum for FieldType { FieldType::SingleSelect, FieldType::MultiSelect, FieldType::Checkbox, + FieldType::URL, ]; values } @@ -8254,82 +8267,83 @@ static file_descriptor_proto_data: &'static [u8] = b"\ extPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x1b\n\ \x08field_id\x18\x02\x20\x01(\tH\0R\x07fieldId\x12)\n\nfield_type\x18\ \x03\x20\x01(\x0e2\n.FieldTypeR\tfieldTypeB\x11\n\x0fone_of_field_id\"\ - \x86\x01\n\x10EditFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\ - \x06gridId\x12\x1b\n\x08field_id\x18\x02\x20\x01(\tH\0R\x07fieldId\x12)\ - \n\nfield_type\x18\x03\x20\x01(\x0e2\n.FieldTypeR\tfieldTypeB\x11\n\x0fo\ - ne_of_field_id\"|\n\x10EditFieldContext\x12\x17\n\x07grid_id\x18\x01\x20\ - \x01(\tR\x06gridId\x12%\n\ngrid_field\x18\x02\x20\x01(\x0b2\x06.FieldR\t\ - gridField\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOption\ - Data\"Z\n\x13FieldTypeOptionData\x12\x19\n\x08field_id\x18\x01\x20\x01(\ - \tR\x07fieldId\x12(\n\x10type_option_data\x18\x02\x20\x01(\x0cR\x0etypeO\ - ptionData\"-\n\rRepeatedField\x12\x1c\n\x05items\x18\x01\x20\x03(\x0b2\ - \x06.FieldR\x05items\"7\n\x12RepeatedFieldOrder\x12!\n\x05items\x18\x01\ - \x20\x03(\x0b2\x0b.FieldOrderR\x05items\"T\n\x08RowOrder\x12\x15\n\x06ro\ - w_id\x18\x01\x20\x01(\tR\x05rowId\x12\x19\n\x08block_id\x18\x02\x20\x01(\ - \tR\x07blockId\x12\x16\n\x06height\x18\x03\x20\x01(\x05R\x06height\"\xb8\ - \x01\n\x03Row\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12@\n\x10cell_b\ - y_field_id\x18\x02\x20\x03(\x0b2\x17.Row.CellByFieldIdEntryR\rcellByFiel\ - dId\x12\x16\n\x06height\x18\x03\x20\x01(\x05R\x06height\x1aG\n\x12CellBy\ - FieldIdEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x1b\n\x05va\ - lue\x18\x02\x20\x01(\x0b2\x05.CellR\x05value:\x028\x01\")\n\x0bRepeatedR\ - ow\x12\x1a\n\x05items\x18\x01\x20\x03(\x0b2\x04.RowR\x05items\"5\n\x11Re\ - peatedGridBlock\x12\x20\n\x05items\x18\x01\x20\x03(\x0b2\n.GridBlockR\ - \x05items\"U\n\x0eGridBlockOrder\x12\x19\n\x08block_id\x18\x01\x20\x01(\ - \tR\x07blockId\x12(\n\nrow_orders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trow\ - Orders\"_\n\rIndexRowOrder\x12&\n\trow_order\x18\x01\x20\x01(\x0b2\t.Row\ - OrderR\x08rowOrder\x12\x16\n\x05index\x18\x02\x20\x01(\x05H\0R\x05indexB\ - \x0e\n\x0cone_of_index\"Q\n\x0fUpdatedRowOrder\x12&\n\trow_order\x18\x01\ - \x20\x01(\x0b2\t.RowOrderR\x08rowOrder\x12\x16\n\x03row\x18\x02\x20\x01(\ - \x0b2\x04.RowR\x03row\"\xc6\x01\n\x11GridRowsChangeset\x12\x19\n\x08bloc\ - k_id\x18\x01\x20\x01(\tR\x07blockId\x123\n\rinserted_rows\x18\x02\x20\ - \x03(\x0b2\x0e.IndexRowOrderR\x0cinsertedRows\x12,\n\x0cdeleted_rows\x18\ - \x03\x20\x03(\x0b2\t.RowOrderR\x0bdeletedRows\x123\n\x0cupdated_rows\x18\ - \x04\x20\x03(\x0b2\x10.UpdatedRowOrderR\x0bupdatedRows\"E\n\tGridBlock\ - \x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12(\n\nrow_orders\x18\x02\ - \x20\x03(\x0b2\t.RowOrderR\trowOrders\"O\n\x04Cell\x12\x19\n\x08field_id\ - \x18\x01\x20\x01(\tR\x07fieldId\x12\x18\n\x07content\x18\x02\x20\x01(\tR\ - \x07content\x12\x12\n\x04data\x18\x03\x20\x01(\tR\x04data\"+\n\x0cRepeat\ - edCell\x12\x1b\n\x05items\x18\x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\ - \x11CreateGridPayload\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\"\ - \x1e\n\x06GridId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"#\n\ - \x0bGridBlockId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"f\n\x10\ - CreateRowPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\ - \"\n\x0cstart_row_id\x18\x02\x20\x01(\tH\0R\nstartRowIdB\x15\n\x13one_of\ - _start_row_id\"\xb6\x01\n\x12InsertFieldPayload\x12\x17\n\x07grid_id\x18\ - \x01\x20\x01(\tR\x06gridId\x12\x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.\ - FieldR\x05field\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etype\ - OptionData\x12&\n\x0estart_field_id\x18\x04\x20\x01(\tH\0R\x0cstartField\ - IdB\x17\n\x15one_of_start_field_id\"|\n\x1cUpdateFieldTypeOptionPayload\ - \x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_i\ - d\x18\x02\x20\x01(\tR\x07fieldId\x12(\n\x10type_option_data\x18\x03\x20\ - \x01(\x0cR\x0etypeOptionData\"d\n\x11QueryFieldPayload\x12\x17\n\x07grid\ - _id\x18\x01\x20\x01(\tR\x06gridId\x126\n\x0cfield_orders\x18\x02\x20\x01\ - (\x0b2\x13.RepeatedFieldOrderR\x0bfieldOrders\"e\n\x16QueryGridBlocksPay\ - load\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_\ - orders\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\x0bblockOrders\"\xa8\ - \x03\n\x15FieldChangesetPayload\x12\x19\n\x08field_id\x18\x01\x20\x01(\t\ - R\x07fieldId\x12\x17\n\x07grid_id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\ - \n\x04name\x18\x03\x20\x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\x04\x20\ - \x01(\tH\x01R\x04desc\x12+\n\nfield_type\x18\x05\x20\x01(\x0e2\n.FieldTy\ - peH\x02R\tfieldType\x12\x18\n\x06frozen\x18\x06\x20\x01(\x08H\x03R\x06fr\ - ozen\x12\x20\n\nvisibility\x18\x07\x20\x01(\x08H\x04R\nvisibility\x12\ - \x16\n\x05width\x18\x08\x20\x01(\x05H\x05R\x05width\x12*\n\x10type_optio\ - n_data\x18\t\x20\x01(\x0cH\x06R\x0etypeOptionDataB\r\n\x0bone_of_nameB\r\ - \n\x0bone_of_descB\x13\n\x11one_of_field_typeB\x0f\n\rone_of_frozenB\x13\ - \n\x11one_of_visibilityB\x0e\n\x0cone_of_widthB\x19\n\x17one_of_type_opt\ - ion_data\"\x9c\x01\n\x0fMoveItemPayload\x12\x17\n\x07grid_id\x18\x01\x20\ - \x01(\tR\x06gridId\x12\x17\n\x07item_id\x18\x02\x20\x01(\tR\x06itemId\ - \x12\x1d\n\nfrom_index\x18\x03\x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_\ - index\x18\x04\x20\x01(\x05R\x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\ - \x0e2\r.MoveItemTypeR\x02ty\"\xb3\x01\n\rCellChangeset\x12\x17\n\x07grid\ - _id\x18\x01\x20\x01(\tR\x06gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\t\ - R\x05rowId\x12\x19\n\x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x126\n\ - \x16cell_content_changeset\x18\x04\x20\x01(\tH\0R\x14cellContentChangese\ - tB\x1f\n\x1done_of_cell_content_changeset**\n\x0cMoveItemType\x12\r\n\tM\ - oveField\x10\0\x12\x0b\n\x07MoveRow\x10\x01*d\n\tFieldType\x12\x0c\n\x08\ - RichText\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\ - \x12\x10\n\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\ - \x0c\n\x08Checkbox\x10\x05b\x06proto3\ + \xa0\x01\n\x10EditFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\ + \x06gridId\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\x07fieldId\x12)\n\n\ + field_type\x18\x03\x20\x01(\x0e2\n.FieldTypeR\tfieldType\x12-\n\x13creat\ + e_if_not_exist\x18\x04\x20\x01(\x08R\x10createIfNotExist\"\x82\x01\n\x16\ + FieldTypeOptionContext\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridI\ + d\x12%\n\ngrid_field\x18\x02\x20\x01(\x0b2\x06.FieldR\tgridField\x12(\n\ + \x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\"v\n\x13Fie\ + ldTypeOptionData\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\ + \x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.FieldR\x05field\x12(\n\x10type\ + _option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\"-\n\rRepeatedField\ + \x12\x1c\n\x05items\x18\x01\x20\x03(\x0b2\x06.FieldR\x05items\"7\n\x12Re\ + peatedFieldOrder\x12!\n\x05items\x18\x01\x20\x03(\x0b2\x0b.FieldOrderR\ + \x05items\"T\n\x08RowOrder\x12\x15\n\x06row_id\x18\x01\x20\x01(\tR\x05ro\ + wId\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07blockId\x12\x16\n\x06he\ + ight\x18\x03\x20\x01(\x05R\x06height\"\xb8\x01\n\x03Row\x12\x0e\n\x02id\ + \x18\x01\x20\x01(\tR\x02id\x12@\n\x10cell_by_field_id\x18\x02\x20\x03(\ + \x0b2\x17.Row.CellByFieldIdEntryR\rcellByFieldId\x12\x16\n\x06height\x18\ + \x03\x20\x01(\x05R\x06height\x1aG\n\x12CellByFieldIdEntry\x12\x10\n\x03k\ + ey\x18\x01\x20\x01(\tR\x03key\x12\x1b\n\x05value\x18\x02\x20\x01(\x0b2\ + \x05.CellR\x05value:\x028\x01\")\n\x0bRepeatedRow\x12\x1a\n\x05items\x18\ + \x01\x20\x03(\x0b2\x04.RowR\x05items\"5\n\x11RepeatedGridBlock\x12\x20\n\ + \x05items\x18\x01\x20\x03(\x0b2\n.GridBlockR\x05items\"U\n\x0eGridBlockO\ + rder\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\x12(\n\nrow_or\ + ders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\"_\n\rIndexRowOrder\ + \x12&\n\trow_order\x18\x01\x20\x01(\x0b2\t.RowOrderR\x08rowOrder\x12\x16\ + \n\x05index\x18\x02\x20\x01(\x05H\0R\x05indexB\x0e\n\x0cone_of_index\"Q\ + \n\x0fUpdatedRowOrder\x12&\n\trow_order\x18\x01\x20\x01(\x0b2\t.RowOrder\ + R\x08rowOrder\x12\x16\n\x03row\x18\x02\x20\x01(\x0b2\x04.RowR\x03row\"\ + \xc6\x01\n\x11GridRowsChangeset\x12\x19\n\x08block_id\x18\x01\x20\x01(\t\ + R\x07blockId\x123\n\rinserted_rows\x18\x02\x20\x03(\x0b2\x0e.IndexRowOrd\ + erR\x0cinsertedRows\x12,\n\x0cdeleted_rows\x18\x03\x20\x03(\x0b2\t.RowOr\ + derR\x0bdeletedRows\x123\n\x0cupdated_rows\x18\x04\x20\x03(\x0b2\x10.Upd\ + atedRowOrderR\x0bupdatedRows\"E\n\tGridBlock\x12\x0e\n\x02id\x18\x01\x20\ + \x01(\tR\x02id\x12(\n\nrow_orders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trow\ + Orders\"5\n\x04Cell\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\ + \x12\x12\n\x04data\x18\x02\x20\x01(\x0cR\x04data\"+\n\x0cRepeatedCell\ + \x12\x1b\n\x05items\x18\x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\x11Cre\ + ateGridPayload\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06\ + GridId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlock\ + Id\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayl\ + oad\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_\ + row_id\x18\x02\x20\x01(\tH\0R\nstartRowIdB\x15\n\x13one_of_start_row_id\ + \"\xb6\x01\n\x12InsertFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\ + \tR\x06gridId\x12\x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.FieldR\x05fie\ + ld\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\ + \x12&\n\x0estart_field_id\x18\x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\ + \x15one_of_start_field_id\"|\n\x1cUpdateFieldTypeOptionPayload\x12\x17\n\ + \x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_id\x18\x02\ + \x20\x01(\tR\x07fieldId\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\ + \x0etypeOptionData\"d\n\x11QueryFieldPayload\x12\x17\n\x07grid_id\x18\ + \x01\x20\x01(\tR\x06gridId\x126\n\x0cfield_orders\x18\x02\x20\x01(\x0b2\ + \x13.RepeatedFieldOrderR\x0bfieldOrders\"e\n\x16QueryGridBlocksPayload\ + \x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orde\ + rs\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\x0bblockOrders\"\xa8\x03\n\ + \x15FieldChangesetPayload\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07f\ + ieldId\x12\x17\n\x07grid_id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\n\x04n\ + ame\x18\x03\x20\x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\x04\x20\x01(\tH\ + \x01R\x04desc\x12+\n\nfield_type\x18\x05\x20\x01(\x0e2\n.FieldTypeH\x02R\ + \tfieldType\x12\x18\n\x06frozen\x18\x06\x20\x01(\x08H\x03R\x06frozen\x12\ + \x20\n\nvisibility\x18\x07\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05w\ + idth\x18\x08\x20\x01(\x05H\x05R\x05width\x12*\n\x10type_option_data\x18\ + \t\x20\x01(\x0cH\x06R\x0etypeOptionDataB\r\n\x0bone_of_nameB\r\n\x0bone_\ + of_descB\x13\n\x11one_of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_\ + of_visibilityB\x0e\n\x0cone_of_widthB\x19\n\x17one_of_type_option_data\"\ + \x9c\x01\n\x0fMoveItemPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\ + \x06gridId\x12\x17\n\x07item_id\x18\x02\x20\x01(\tR\x06itemId\x12\x1d\n\ + \nfrom_index\x18\x03\x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_index\x18\ + \x04\x20\x01(\x05R\x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\x0e2\r.Mo\ + veItemTypeR\x02ty\"\xb3\x01\n\rCellChangeset\x12\x17\n\x07grid_id\x18\ + \x01\x20\x01(\tR\x06gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05ro\ + wId\x12\x19\n\x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x126\n\x16cell_\ + content_changeset\x18\x04\x20\x01(\tH\0R\x14cellContentChangesetB\x1f\n\ + \x1done_of_cell_content_changeset**\n\x0cMoveItemType\x12\r\n\tMoveField\ + \x10\0\x12\x0b\n\x07MoveRow\x10\x01*m\n\tFieldType\x12\x0c\n\x08RichText\ + \x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\ + \x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08C\ + heckbox\x10\x05\x12\x07\n\x03URL\x10\x06b\x06proto3\ "; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; diff --git a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto index 5913b0ca0e..f06d84e5b8 100644 --- a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto +++ b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto @@ -35,17 +35,19 @@ message GetEditFieldContextPayload { } message EditFieldPayload { string grid_id = 1; - oneof one_of_field_id { string field_id = 2; }; + string field_id = 2; FieldType field_type = 3; + bool create_if_not_exist = 4; } -message EditFieldContext { +message FieldTypeOptionContext { string grid_id = 1; Field grid_field = 2; bytes type_option_data = 3; } message FieldTypeOptionData { - string field_id = 1; - bytes type_option_data = 2; + string grid_id = 1; + Field field = 2; + bytes type_option_data = 3; } message RepeatedField { repeated Field items = 1; @@ -93,8 +95,7 @@ message GridBlock { } message Cell { string field_id = 1; - string content = 2; - string data = 3; + bytes data = 2; } message RepeatedCell { repeated Cell items = 1; @@ -166,4 +167,5 @@ enum FieldType { SingleSelect = 3; MultiSelect = 4; Checkbox = 5; + URL = 6; } diff --git a/shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs index 3f727faf59..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| { @@ -175,7 +194,7 @@ impl GridBlockMetaPad { match cal_diff::(old, new) { None => Ok(None), Some(delta) => { - tracing::debug!("[GridBlockMeta] Composing delta {}", delta.to_delta_str()); + tracing::trace!("[GridBlockMeta] Composing delta {}", delta.to_delta_str()); // tracing::debug!( // "[GridBlockMeta] current delta: {}", // self.delta.to_str().unwrap_or_else(|_| "".to_string()) diff --git a/shared-lib/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) diff --git a/shared-lib/lib-infra/Cargo.toml b/shared-lib/lib-infra/Cargo.toml index 9659490959..8b74a68a4c 100644 --- a/shared-lib/lib-infra/Cargo.toml +++ b/shared-lib/lib-infra/Cargo.toml @@ -16,6 +16,7 @@ rand = "0.8.5" serde = { version = "1.0", features = ["derive"]} serde_json = "1.0" + cmd_lib = { version = "1", optional = true } protoc-rust = { version = "2", optional = true } walkdir = { version = "2", optional = true } diff --git a/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs b/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs index a65d4d5bd9..a44f0cb267 100644 --- a/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs +++ b/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs @@ -153,27 +153,22 @@ pub fn check_pb_dart_plugin() { let output = Command::new("sh").arg("-c").arg("echo $PATH").output(); let paths = String::from_utf8(output.unwrap().stdout) .unwrap() - .split(":") + .split(':') .map(|s| s.to_string()) .collect::>(); paths.iter().for_each(|s| msg.push_str(&format!("{}\n", s))); - match Command::new("sh").arg("-c").arg("which protoc-gen-dart").output() { - Ok(output) => { - msg.push_str(&format!( - "Installed protoc-gen-dart path: {:?}\n", - String::from_utf8(output.stdout).unwrap() - )); - } - Err(_) => {} + if let Ok(output) = Command::new("sh").arg("-c").arg("which protoc-gen-dart").output() { + msg.push_str(&format!( + "Installed protoc-gen-dart path: {:?}\n", + String::from_utf8(output.stdout).unwrap() + )); } - msg.push_str(&format!("✅ You can fix that by adding:")); - msg.push_str(&format!("\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n",)); - msg.push_str(&format!( - "to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)" - )); + msg.push_str(&"✅ You can fix that by adding:".to_string()); + msg.push_str(&"\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n".to_string()); + msg.push_str(&"to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)".to_string()); panic!("{}", msg) } }